Objectives

In this section we will learn how to generate graphics in R.

The material presented is here largely based on the material presented in:

Install and load packages

# Clear the workspace
rm(list = ls())
# List of package needed for this workshop
reqpkg <- c("ggplot2", "ggrepel", "ggthemes", "grid", "gridExtra", 
            "RColorBrewer")
# Check if the packages are installed:
inpkg = installed.packages()[, "Package"] #installed packages
neededpkg = reqpkg[!reqpkg %in% inpkg]
if(length(neededpkg) > 0){
  stop(paste("\n Need to install the following package:", neededpkg))
}
# Load all required packages and show version
for(i in reqpkg){
    print(paste(i, "version:", packageVersion(i)))
    library(i, quietly=TRUE, verbose=FALSE, warn.conflicts=FALSE, character.only=TRUE)
}
[1] "ggplot2 version: 2.1.0"
[1] "ggrepel version: 0.5.1"
[1] "ggthemes version: 3.2.0"
[1] "grid version: 3.3.0"
[1] "gridExtra version: 2.2.1"
[1] "RColorBrewer version: 1.1.2"

If you haven’t done that already, download the workshop materials:

setwd("/path/to/dir/")
download.file("/url/to/workshop/materials", "R_workshop.zip")
unzip("R_workshop.zip")

Introduction

Instructions:

This course assumes that you:

What is ggplot2?

ggplot2 is a plotting system for R, based on the grammar of graphics. It takes care of many of the fiddly details that make plotting a hassle (like drawing legends) as well as providing a powerful model of graphics that makes it easy to produce complex multi-layered graphics. 1

Advantages of ggplot2:

Weaknesses of ggplot2 (what the package should not be used for):

What is the grammar of graphics?

It is a concept coined by Leland Wilkinson in 2005.

The basic idea: a plot is defined by independent building blocks, which combined create just about any kind of visualization you want.

The building blocks of a graph include:

The structure of ggplot object

The ggplot() function is used to initialize the basic graph structure. It cannot produce the plot we want by itself. Instead, we need to add extra building blocks it. The structure of a ggplot looks like this:

ggplot(data = <default data set>, 
       aes(x = <default x axis variable>,
           y = <default y axis variable>,
           ... <other default aesthetic mappings>),
       ... <other plot defaults>) +
  
  geom_<geom type>(aes(size = <size variable for this geom>, 
                       ... <other aesthetic mappings>),
                   data = <data for this point geom>,
                   stat = <statistic string or function>,
                   position = <position string or function>,
                   color = <"fixed color specification">,
                  ... <other arguments, possibly passed to the _stat_ function) +

  scale_<aesthetic>_<type>(name = <"scale label">,
                           breaks = <where to put tick marks>,
                           labels = <labels for tick marks>,
                           ... <other options for the scale>) +

  theme(plot.background = element_rect(fill = "gray"),
        ... <other theme elements>)

This chunk of code might seem confusing, but by the end of this workshop you should be able to understand each of the components.

The basic idea is that you specify different parts of the plot, and add them together using the + operator.

ggplot2 vs base graphics:

ggplot2 compared to base graphics is:

Example 1: History of unemployemnt

data("economics")
str(economics)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   574 obs. of  6 variables:
 $ date    : Date, format: "1967-07-01" "1967-08-01" "1967-09-01" "1967-10-01" ...
 $ pce     : num  507 510 516 513 518 ...
 $ pop     : int  198712 198911 199113 199311 199498 199657 199808 199920 200056 200208 ...
 $ psavert : num  12.5 12.5 11.7 12.5 12.5 12.1 11.7 12.2 11.6 12.2 ...
 $ uempmed : num  4.5 4.7 4.6 4.9 4.7 4.8 5.1 4.5 4.1 4.6 ...
 $ unemploy: int  2944 2945 2958 3143 3066 3018 2878 3001 2877 2709 ...
head(economics)
Source: local data frame [6 x 6]

        date   pce    pop psavert uempmed unemploy
      <date> <dbl>  <int>   <dbl>   <dbl>    <int>
1 1967-07-01 507.4 198712    12.5     4.5     2944
2 1967-08-01 510.5 198911    12.5     4.7     2945
3 1967-09-01 516.3 199113    11.7     4.6     2958
4 1967-10-01 512.9 199311    12.5     4.9     3143
5 1967-11-01 518.1 199498    12.5     4.7     3066
6 1967-12-01 525.8 199657    12.1     4.8     3018

Base graphics with a simple plot() function:

plot(unemploy/pop ~ date, data = economics,  type = "l")

A similar plot using ggplot2

ggplot(data = economics, aes(x = date, y = unemploy/pop)) + geom_line()

Note that, ggplot() by itself does not plot the data.

ggplot(data = economics, aes(x = date, y = unemploy/pop))

You need to add the lines object.

ggplot(data = economics, aes(x = date, y = unemploy/pop)) + geom_line()

… and possibly change the background color from default gray to customized white.

ggplot(data = economics, aes(x = date, y = unemploy/pop)) + geom_line() +
  theme_bw()

What if we want to compare the trend from year 2009 to 2014?

Add two variables one for year and one for day of the year:

economics$dayOftheYear <- format(economics$date, format="%m-%d")
economics$dayOftheYear <- as.Date(economics$dayOftheYear, format="%m-%d")
economics$year <- format(economics$date, format="%Y")
head(economics)
Source: local data frame [6 x 8]

        date   pce    pop psavert uempmed unemploy dayOftheYear  year
      <date> <dbl>  <int>   <dbl>   <dbl>    <int>       <date> <chr>
1 1967-07-01 507.4 198712    12.5     4.5     2944   2016-07-01  1967
2 1967-08-01 510.5 198911    12.5     4.7     2945   2016-08-01  1967
3 1967-09-01 516.3 199113    11.7     4.6     2958   2016-09-01  1967
4 1967-10-01 512.9 199311    12.5     4.9     3143   2016-10-01  1967
5 1967-11-01 518.1 199498    12.5     4.7     3066   2016-11-01  1967
6 1967-12-01 525.8 199657    12.1     4.8     3018   2016-12-01  1967

Using base graphics:

plot(unemploy/pop ~ dayOftheYear, data = subset(economics, year == 2009), 
     ylim = c(0.025, 0.05), type = "l")
lines(unemploy/pop ~ dayOftheYear, col = "red", data = subset(economics, year == 2014))
legend("topleft",
       c("2008", "2014"), title="Year",
       col=c("black", "red"),
       pch=c(1, 1))

Using ggplot2:

ggplot(data = subset(economics, year %in% c(2014, 2009)), 
       aes(x = dayOftheYear, y = unemploy/pop)) + 
  geom_line(aes(color = year)) 

Note that there is no need to specify the legend! It is produced automatically in ggplot2.

It is easy to even plot all the years together:

ggplot(data = economics, aes(x = dayOftheYear, y = unemploy/pop)) + 
  geom_line(aes(color = year))

Example 2: diamonds dataset

We will now load a diamonds data set that is included in with the ggplot2 package.

The data set contains the prices and other attributes of almost 54,000 diamonds. You can call ?diamonds to learn more about the available attributes.

data("diamonds")
str(diamonds)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   53940 obs. of  10 variables:
 $ carat  : num  0.23 0.21 0.23 0.29 0.31 0.24 0.24 0.26 0.22 0.23 ...
 $ cut    : Ord.factor w/ 5 levels "Fair"<"Good"<..: 5 4 2 4 2 3 3 3 1 3 ...
 $ color  : Ord.factor w/ 7 levels "D"<"E"<"F"<"G"<..: 2 2 2 6 7 7 6 5 2 5 ...
 $ clarity: Ord.factor w/ 8 levels "I1"<"SI2"<"SI1"<..: 2 3 5 4 2 6 7 3 4 5 ...
 $ depth  : num  61.5 59.8 56.9 62.4 63.3 62.8 62.3 61.9 65.1 59.4 ...
 $ table  : num  55 61 65 58 58 57 57 55 61 61 ...
 $ price  : int  326 326 327 334 335 336 336 337 337 338 ...
 $ x      : num  3.95 3.89 4.05 4.2 4.34 3.94 3.95 4.07 3.87 4 ...
 $ y      : num  3.98 3.84 4.07 4.23 4.35 3.96 3.98 4.11 3.78 4.05 ...
 $ z      : num  2.43 2.31 2.31 2.63 2.75 2.48 2.47 2.53 2.49 2.39 ...
head(diamonds)
Source: local data frame [6 x 10]

  carat       cut  color clarity depth table price     x     y     z
  <dbl>    <fctr> <fctr>  <fctr> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
1  0.23     Ideal      E     SI2  61.5    55   326  3.95  3.98  2.43
2  0.21   Premium      E     SI1  59.8    61   326  3.89  3.84  2.31
3  0.23      Good      E     VS1  56.9    65   327  4.05  4.07  2.31
4  0.29   Premium      I     VS2  62.4    58   334  4.20  4.23  2.63
5  0.31      Good      J     SI2  63.3    58   335  4.34  4.35  2.75
6  0.24 Very Good      J    VVS2  62.8    57   336  3.94  3.96  2.48

It is easy to plot the distribution of the diamonds prices with base graphics

hist(diamonds$price)

as well as with ggplot2

ggplot(diamonds, aes(x = price)) + geom_histogram()
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Now we subset the data to show the relationship between the diamonds weights (carat = 200 mg) and their prices ($):

set.seed(12345) # Make the sample reproducible
dsmall <- diamonds[sample(nrow(diamonds), 200), ]

Base graphics:

colorMap <- data.frame(color = rainbow(length(unique(dsmall$color))))
rownames(colorMap) <- unique(dsmall$color)
plot(price ~ carat, data = dsmall, col = colorMap[dsmall$color, "color"])
legend(x = 'bottomright', 
       legend = rownames(colorMap),
       col = colorMap$color, pch = par("pch"), bty = 'n', xjust = 1)

ggplot2:

ggplot(data = dsmall, aes(x = carat, y = price, color = color)) + geom_point()

Geometric objects and Aesthetics

Geometic Objects (geom):

Geometric objects are the actual items we put on a plot. Examples include:

  • points (geom_point, for scatter plots, dot plots, etc)
  • lines (geom_line, for time series, trend lines, etc)
  • boxplot (geom_boxplot, for, well, boxplots!)

A plot must have at least one geom; there is no upper limit. You can add a geom to a plot using the + operator.

You can get a list of available geometric objects using the code below:

help.search("geom_", package = "ggplot2")

or simply type geom_<tab> in any good R IDE (such as Rstudio or ESS) to see a list of functions starting with geom_.

Aesthetic Mapping

In ggplot an aesthetic mapping, defined with aes(), describes how variables are mapped to visual properties or aesthetics.

Examples of aesthetics are:

  • position (i.e., on the x and y axes)
  • color (“outside” color)
  • fill (“inside” color)
  • shape (of points)
  • linetype
  • size

Each type of geom accepts only a subset of all aesthetics. Refer to the geom help pages to see what mappings each geom accepts.

Scatter plots (geom_points)

p1 <- ggplot(dsmall, aes(x = carat, y = price))
p1 + geom_point()

p1 + geom_point(aes(color = color))

p1 + geom_point(aes(shape = cut))

p1 + geom_point(aes(shape = cut, color = color))

Aesthetic Mapping vs Assignment

Note that variables are mapped to aesthetics with the aes() function, while fixed aesthetics are set outside the aes() call.

This sometimes leads to confusion, as in this example:

ggplot(data = dsmall, aes(x = carat, y = price)) + 
  geom_point(aes(size = 2), # this is conceptually wrong since 2 is not a variable
             color = "darkgreen") # this is ok since you might want all points to be green.

ggplot(data = dsmall, aes(x = carat, y = price)) + 
  geom_point(aes(fill = cut), size = 2, color = "black", shape = 25)

Available shape configurations

## A look at all 25 symbols
df2 <- data.frame(x = 1:5 , y = 1:25, z = 1:25)
s <- ggplot(df2, aes(x = x, y = y))
s + geom_point(aes(shape = z), size = 3, colour = "blue") +
  scale_shape_identity()

## While all symbols have a foreground colour, symbols 19-25 also take a
## background colour (fill)
s + geom_point(aes(shape = z), size = 3, colour = "darkgreen", fill = "orange") +
  scale_shape_identity()

Data transformations

We can plot the transformed data by calling a function on the variable. For example here we show the log transformed data.

ggplot(dsmall, aes(x = log(carat), y = log(price))) + geom_point()

Text labels

Use even a smaller subset to avoid cluttering:

set.seed(12345) # Make the sample reproducible
dsmall2 <- diamonds[sample(nrow(diamonds), 100), ]
p2 <- ggplot(dsmall2, aes(x = log(carat), y = log(price)))
p2 + geom_text(aes(label = color))

p2 + geom_label(aes(label = color))

The ggreplel gives an easy way to annotate the labels when they are densely packed.

library(ggrepel)
p2 + geom_point() + geom_text_repel(aes(label=color), size = 3)

It doesn’t work that well though if you have too many points clustered together, then the lines pointing to the points will extend too far way, to make room for all the labels.

p1 + geom_point() + geom_text_repel(aes(label=color), size = 3)

In these cases you should choose to label only a subset of points.

set.seed(123456)
subsetData <- subset(dsmall, sample(c(TRUE, FALSE), nrow(dsmall), replace = TRUE,
                                    prob = c(0.2, 0.8)))
p1 + geom_point() + 
  geom_text_repel(data = subsetData, aes(label=color), size = 5, col = "Blue")

The Economist Data

For practice, you will try to recreate a plot published in the Economist issue of July 20th, 2016 reflecting the relationship between well-being and financial inclusion.

Graph source: http://www.economist.com/blogs/graphicdetail/2016/07/daily-chart-13

You will generate this figure step by step through a series of included exercises using the tools we’ve just learned and will learn about.

The data for the exercises is available in the dataSets/EconomistData.csv file. Read it in with the following commands:

dat <- read.csv("./data/EconomistData.csv")
head(dat)
    Country SEDA.Current.level SEDA.Recent.progress Wealth.to.well.being.coefficient Growth.to.well.being.coefficient
1   Albania               50.0                 63.3                             1.27                             1.31
2   Algeria               40.6                 46.5                             0.87                             1.03
3    Angola               17.8                 76.2                             0.54                             1.21
4 Argentina               54.1                 49.1                             0.91                             0.89
5   Armenia               43.8                 46.0                             1.25                             1.11
6 Australia               87.9                 40.9                             1.07                             0.92
  Percent.of.15plus.with.bank.account                  EPI_regions                        Region
1                            37.98635    Central and Eastern Europ                        Europe
2                            50.47579 Middle East and North Africa    Middle East & North Africa
3                            29.31812           Sub-Saharan Africa            Sub-Saharan Africa
4                            50.19730    Latin America and Caribbe Latin America & the Caribbean
5                            17.66907 Middle East and North Africa    Middle East & North Africa
6                            98.85957    East Asia and the Pacific                       Oceania

The original sources for this data are:

The countries assignment to regions is based on the EPI_regions column in the countryExData data.frame from rworldmap package. The Region variable was matched with the categories in the Economist plot.

Exercise I

For the EconomistData.csv do the following:

  1. Create a scatter plot with percent of people over the age of 15 with a bank account on the x axis and the SEDA score on the y axis.
  2. Color the points in the previous plot blue.
  3. Color the points in the previous plot according to the Region.
  4. Create boxplots of SEDA scores by Region.
  5. Overlay points on top of the box plots
# (...?)

Statistical Transformations

Now, we will go back to the diamonds data set and return to the Economist plot later.

So far we have only dealt with the (x,y) type of plots (scatter plots or line plots) where each of the point has its corresponding (x,y) coordinate.

Sometimes, however, we are more interested in plots that require some statistical transformations. The transformations might map a raw datapoint or a group of datapoints to other values. Examples of plots involving statistical transformations:

These types of plots require some statistical transformations. For example:

Boxplots and jittered points

ggplot(data = diamonds, aes(x = color, y =price/carat)) +
  geom_jitter()

j1 <- ggplot(data = diamonds, aes(x = color, y =price/carat)) +
  geom_jitter(alpha = I(1/5))
j2 <- ggplot(data = diamonds, aes(x = color, y =price/carat)) +
  geom_jitter(alpha = I(1/50))
j3 <- ggplot(data = diamonds, aes(x = color, y =price/carat)) +
  geom_jitter(alpha = I(1/200))
grid.arrange(j1, j2, j3, ncol = 3)

Here we used grid.arrange() for the package gridExtra to display multiple plots in the same line.

Sometimes less is more…

ggplot(data = diamonds, aes(x = color, y =price/carat)) +
  geom_boxplot()

Histogram and density plots

Below we plot the distribution of the weights (carat) of the diamonds.

h <- ggplot(data = diamonds, aes(x = carat)) + geom_histogram()
d <- ggplot(data = diamonds, aes(x = carat)) + geom_density()
grid.arrange(h, d, ncol = 2)
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

  • For the density plot, the adjust argument controls the degree of smoothness (high values of adjust produce smoother plots).
  • For the histogram, the binwidth or bins argument controls the amount of smoothing by setting the bin size or the number of bins. (Break points can also be specified explicitly, using the breaks argument.)
p <- ggplot(data = diamonds, aes(x = carat)) + xlim(0, 3)
h1 <- p + geom_histogram(binwidth = 1) 
h2 <- p + geom_histogram(binwidth = 0.1) 
h3 <- p + geom_histogram(binwidth = 0.01) 
grid.arrange(h1, h2, h3, ncol = 3)
Warning: Removed 32 rows containing non-finite values (stat_bin).
Warning: Removed 32 rows containing non-finite values (stat_bin).
Warning: Removed 32 rows containing non-finite values (stat_bin).

d1 <- p + geom_density(adjust = 5) 
d2 <- p + geom_density(adjust = 1) 
d3 <- p + geom_density(adjust = 1/5) 
grid.arrange(d1, d2, d3, ncol = 3)
Warning: Removed 32 rows containing non-finite values (stat_density).
Warning: Removed 32 rows containing non-finite values (stat_density).
Warning: Removed 32 rows containing non-finite values (stat_density).

The histograms can be broken down into groups. Here we show grouping by diamonds cut.

h <- p + geom_histogram(aes(fill = cut), position = "dodge", bins = 10)
d <- p + geom_density(aes(color = cut))
grid.arrange(h, d, ncol = 2)
Warning: Removed 32 rows containing non-finite values (stat_bin).
Warning: Removed 4 rows containing missing values (geom_bar).
Warning: Removed 32 rows containing non-finite values (stat_density).

Instead of the marginal distribution, we can plot the components stacked on top of each other to see the contribution from each of group.

h <- p + geom_histogram(aes(fill = cut), position = "stack")
d <- p + geom_density(aes(fill = cut), position = "stack")
grid.arrange(h, d, ncol = 2)
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
Warning: Removed 32 rows containing non-finite values (stat_bin).
Warning: Removed 32 rows containing non-finite values (stat_density).

Bar charts

The discrete analogue of histogram is the bar chart, geom = "bar". Instead of partitioning the values into bins like histograms, the bar geom counts the number of instances of each discrete class. The counts are then plotted as columns for each distinct class.

If you’d like to tabulate class members in some other way (rather than count), e.g. by summing up a continuous variable, you can use the weight aesthetic.

b1 <- ggplot(diamonds, aes(x = color)) + geom_bar()
b2 <- ggplot(diamonds, aes(x = color)) + geom_bar(aes(weight = carat)) + ylab("carat")
grid.arrange(b1, b2, ncol = 2)

The left plot shows counts and the right plot is the count weighted by weight = carat to show the total weight of diamonds of each color.

Thus, you don’t need to tabulate your values beforehand, as with barchart in base R. However, if you did already summarize your data, you can still use geom_bar but using another statistical transformation stat = "identity rather than the default stat = "count".

diamonds.mean <- aggregate(diamonds["carat"], diamonds["color"], FUN=mean)
rbind(head(diamonds.mean), tail(diamonds.mean))
   color     carat
1      D 0.6577948
2      E 0.6578667
3      F 0.7365385
4      G 0.7711902
5      H 0.9117991
6      I 1.0269273
21     E 0.6578667
31     F 0.7365385
41     G 0.7711902
51     H 0.9117991
61     I 1.0269273
7      J 1.1621368

The default option will generate an error:

ggplot(diamonds.mean, aes(x=color, y=carat)) + 
  geom_bar()
Error: stat_count() must not be used with a y aesthetic.

Thus, you need to use the following:

ggplot(diamonds.mean, aes(x=color, y=carat)) + 
  geom_bar(stat="identity")

diamonds.sum <- aggregate(diamonds["carat"], diamonds["color"], FUN=sum)
ggplot(diamonds.sum, aes(x=color, y=carat)) +  geom_bar(stat="identity")

Note that this is the same plot as the one generated with the weight aesthetic, which is exactly what we should expect.

Prediction lines

We can include a regression line to plot by simply adding the line with the fitted y-values from a prediction model:

dsmall$pred.price <- predict(lm(price ~ carat, data = dsmall))
p1 <- ggplot(dsmall, aes(x = carat, y = price))
p1 + geom_point(aes(color = color)) + geom_line(aes(y = pred.price))

Smoothers

If you have a scatterplot with many data points, it can be hard to see exactly what trend is shown by the data. In this case you may want to add a smoothed line to the plot. The smooth geom includes a line and a ribbon.

ggplot(data = diamonds, aes(x = carat, y = price)) +
  geom_point() + geom_smooth()

For our small subset of the diamonds data set we have:

p1 + geom_point() + geom_smooth()

Changing the span argument, we can obtain more or less wiggly curve (smaller span results in more wiggliness).

grid.arrange(p1 + geom_point() + geom_smooth(span = 0.2),
             p1 + geom_point() + geom_smooth(span = 0.7), ncol = 2)

  • The default method used in geom_smooth with small number of observations (n < 1000) is method = "loess" which uses a smooth local regression. More details about the algorithm used can be found in ?loess.
  • Loess does not work well for large datasets (it’s \(O(n^2)\) in memory), and so an alternative smoothing algorithm is used when n is greater than 1,000.

Exercise II

  1. Re-create a scatter plot with percent of people aged 15+ with a bank account on the x axis and SEDA current level score on the y axis (as you did in the previous exercise).
  2. Overlay a smoothing line on top of the scatter plot using the lm method. Hint: see ?stat_smooth.
  3. Overlay a smoothing line on top of the scatter plot using the default method.
  4. Overlay a smoothing line on top of the scatter plot using the default loess method, but make it less smooth. Hint: see ?loess.
# (...?)

Scales

Aesthetic Mapping Variable Scaling

Aesthetic mapping (i.e., with aes()) is responsible for assigning an aesthetic to a variable. It doesn’t however specify how mapping should be done.

For example, aes(shape = x) or aes(color = z) do not specify what shapes or what colors should be used. To choose colors/shapes/sizes etc. you need to modify the corresponding scale.

In ggplot2 scales include:

  • position
  • color and fill
  • size
  • shape
  • line type

Scales are modified with a series of functions using a scale_<aesthetic>_<type> naming scheme. Try typing scale_<tab> to see a list of scale modification functions.

Common Scale Arguments:

  • name: the first argument gives the axis or legend title
  • limits: the minimum and maximum of the scale
  • breaks: the points along the scale where labels should appear
  • labels: the labels that appear at each break

Scale: axes

Square root transformation of the y-axis:

p1 <- ggplot(dsmall, aes(x = carat, y = price)) 
p1 + geom_point() + scale_y_sqrt()

Log bas 10 transformation of the y-axis:

p1 + geom_point() + scale_y_log10()

Log base 10 transformation of x and y axes:

p1 + geom_point() + scale_y_log10() + scale_x_log10()

Note that the above produces the same points as:

ggplot(dsmall, aes(x = log(carat), y = log(price))) + geom_point()

but with different values on the axes.

Scale: shapes

p1 + geom_point(aes(shape = cut), size = 3)  

p1 + geom_point(aes(shape = cut), size = 3) + 
  scale_shape_manual(values = c(1:5))

Scale: colors

To choose specific colors for discrete variables we can use scale_color_manual

p1 + geom_point(aes(color = cut), size = 3) + 
  scale_color_manual(values = c("red", "orange", "yellow", "green", "blue"))

For continuous variables you can also use scale_color_gradient, and specify the ends of the spectrum:

p1 + geom_point(aes(color = price), size = 3) + 
  scale_color_gradient(low = "blue", high = "red")

scale_color_brewer is a very useful function that can be used to set colors for discrete variables. It gives you a choice between many predefined and pretty color palettes.

p1 + geom_point(aes(color = cut), size = 3) + 
  scale_color_brewer(palette = "Set2")

Unfortunately scale_color_brewer doesn’t work for continuous variables:

p1 + geom_point(aes(shape = price), size = 3) + 
  scale_color_brewer(palette = "Spectral")
Error: A continuous variable can not be mapped to shape

Thankfully, we can get around this issue using the RColorBrewer package and using scale_color_gradientn:

library(RColorBrewer)
p1 + geom_point(aes(color = price), size = 3) + 
  scale_color_gradientn(colours = brewer.pal(name = "Spectral", n = 10))

… and if you are a true indie person, you can is even check out the color schemes based on Wes Anderson movies:

#install.packages("wesanderson")
library(wesanderson)
names(wes_palettes)
 [1] "GrandBudapest"  "Moonrise1"      "Royal1"         "Moonrise2"      "Cavalcanti"     "Royal2"         "GrandBudapest2"
 [8] "Moonrise3"      "Chevalier"      "Zissou"         "FantasticFox"   "Darjeeling"     "Rushmore"       "BottleRocket"  
[15] "Darjeeling2"   

For discrete:

p1 + geom_point(aes(color = cut), size = 3) + 
  scale_color_manual(values = wes_palette("Darjeeling", n = 5))

For continuous:

p1 + geom_point(aes(color = price), size = 3) + 
  scale_color_gradientn(colours = wes_palette("Darjeeling", 100, type = "continuous"))

You can also scale the values of the variable corresponding to color.

p1 + geom_point(aes(color = price), size = 3) + 
  scale_color_gradient(low = "blue", high = "red", trans = "log10")

and add your own breaks

p1 + geom_point(aes(color = price), size = 3) + 
  scale_color_gradient(low = "blue", high = "red", trans = "log10",
                       breaks = c(1000, 2000, 5000, 10000),
                       labels = c("  1000", "  2000", "  5000", "10000")) 
  

Exercise III

  1. For the scatter plot of % of ppl aged 15+ with bank account vs SEDA score colored by region, generated in Exercise I.3 modify the color scale to use specific values of your choosing. Hint: see ?scale_color_manual.
# (...?)

Faceting

p <-  ggplot(diamonds, aes(x = carat))
p + geom_histogram()
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

p + geom_histogram() + facet_wrap(~ color)
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

p + geom_histogram() + facet_grid(cut ~ color)
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

(p <- ggplot(data = diamonds[sample(nrow(diamonds), 1000), ], 
             aes(x = carat, y = price)) +
  geom_point(aes(text = paste("Clarity:", clarity)), size = 1) +
  geom_smooth(aes(colour = cut, fill = cut)) + facet_wrap(~ cut))

Excercise IV

  1. Facet by region (~ Region) the the Economist plot from Exercise III.
# (... ?)

Finish the Economist plot.

To complete the graph we need to:

Change order of the Regions

dat$Region <- as.character(dat$Region)
dat$Region <- factor(dat$Region, 
                     levels = c("Europe", "Asia", "Oceania", 
                                "North America", 
                                "Latin America & the Caribbean", 
                                "Middle East & North Africa",
                                "Sub-Saharan Africa"),
                     labels = c("Europe", "Asia", "Oceania", 
                                "North America", 
                                "Latin America & \n the Caribbean", 
                                "Middle East & \n North Africa",
                                "Sub-Saharan \n Africa"))
ggplot(dat, aes(Percent.of.15plus.with.bank.account, SEDA.Current.level)) + 
  geom_point(aes(color = Region))

Add the linear trend

# (...?)

Change the axes ratio.

Hint ?coord_fixed

# (...?)

Change the color scheme

Use the following colors c("#28AADC","#F2583F", "#76C0C1","#24576D", "#248E84","#DCC3AA", "#96503F")

# (...?)

Add a title and format the axes

Check ?scale_x_continuous and ?scale_y_continuous documentation. To add a title use ggtitle().

# (...?)

Change the background and theme

You can check out the ggthemes package which implement the themes that make your plots look like they came from:

  • Base graphics
  • Tableau
  • Excel
  • Stata
  • the Economist
  • Wall Street Journal
  • Edward Tufte
  • Nate Silver’s Fivethirtyeight
  • etc.

use the one than mimics the Economist.

library(ggthemes)
# (...?)

Format the legend

Using theme() and the arguments like: legend.position, legend.direction, legend.text, plot.margin

# (...?)

Add point labels

Add labels to the following subset of the countries:

pointsToLabel <- c("Yemen", "Iraq", "Egypt", "Jordan", "Chad", "Congo", 
                   "Angola", "Albania", "Zimbabwe", "Uganda", "Nigeria",
                   "Uruguay", "Kazakhstan", "India", "Turkey", "South Africa",
                   "Kenya", "Russia", "Brazil", "Chile", "Saudi Arabia", 
                   "Poland", "China", "Serbia", "United States", "United Kingdom")

Use geom_text_repel()

# (...?)

Add notes to the bottom and save the plot

Use grid.text()

# (...?)

plotly for interactive plotting

You can also easily generate an interactive plot by calling ggplotly() function from plotly package.

#install.packages("plotly")
library(plotly)
#ggplotly(pEconomist)

Similar to the original:

What we have learned so far:

Additional resources:


  1. http://ggplot2.org/

  2. https://www.bcgperspectives.com/content/articles/growth-globalization-private-sector-opportunity-improve-well-being-2016-economic-development-assessment/

LS0tCnRpdGxlOiAiR2VuZXJhdGluZyBncmFwaGljcyB3aXRoIFIiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCiMgT2JqZWN0aXZlcwoKSW4gdGhpcyBzZWN0aW9uIHdlIHdpbGwgbGVhcm4gaG93IHRvIGdlbmVyYXRlIGdyYXBoaWNzIGluIFIuCgpUaGUgbWF0ZXJpYWwgcHJlc2VudGVkIGlzIGhlcmUgbGFyZ2VseSBiYXNlZCBvbiB0aGUgbWF0ZXJpYWwgcHJlc2VudGVkIGluOgoKKiBbd29ya3Nob3Agbm90ZXNdKGh0dHA6Ly90dXRvcmlhbHMuaXEuaGFydmFyZC5lZHUvUi9SZ3JhcGhpY3MvUmdyYXBoaWNzLmh0bWwpIGJ5IERhdGEgU2NpZW5jZSBTZXJ2aWNlcyBhdCBIYXJ2YXJkIElRU1MgCiogYW5kIFtDaGFwdGVyIDJdKGh0dHA6Ly9nZ3Bsb3QyLm9yZy9ib29rL3FwbG90LnBkZikgZnJvbSBIYWRsZXkgV2lja2hhbSdzIGJvb2sgb24gZ2dwbG90Mi4KCiMgSW5zdGFsbCBhbmQgbG9hZCBwYWNrYWdlcwoKYGBge3IgY2hlY2staW5zdGFsbC1sb2FkLXBhY2thZ2VzLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQojIENsZWFyIHRoZSB3b3Jrc3BhY2UKcm0obGlzdCA9IGxzKCkpCgojIExpc3Qgb2YgcGFja2FnZSBuZWVkZWQgZm9yIHRoaXMgd29ya3Nob3AKcmVxcGtnIDwtIGMoImdncGxvdDIiLCAiZ2dyZXBlbCIsICJnZ3RoZW1lcyIsICJncmlkIiwgImdyaWRFeHRyYSIsIAogICAgICAgICAgICAiUkNvbG9yQnJld2VyIikKCiMgQ2hlY2sgaWYgdGhlIHBhY2thZ2VzIGFyZSBpbnN0YWxsZWQ6CmlucGtnID0gaW5zdGFsbGVkLnBhY2thZ2VzKClbLCAiUGFja2FnZSJdICNpbnN0YWxsZWQgcGFja2FnZXMKbmVlZGVkcGtnID0gcmVxcGtnWyFyZXFwa2cgJWluJSBpbnBrZ10KaWYobGVuZ3RoKG5lZWRlZHBrZykgPiAwKXsKICBzdG9wKHBhc3RlKCJcbiBOZWVkIHRvIGluc3RhbGwgdGhlIGZvbGxvd2luZyBwYWNrYWdlOiIsIG5lZWRlZHBrZykpCn0KCiMgTG9hZCBhbGwgcmVxdWlyZWQgcGFja2FnZXMgYW5kIHNob3cgdmVyc2lvbgpmb3IoaSBpbiByZXFwa2cpewoJcHJpbnQocGFzdGUoaSwgInZlcnNpb246IiwgcGFja2FnZVZlcnNpb24oaSkpKQoJbGlicmFyeShpLCBxdWlldGx5PVRSVUUsIHZlcmJvc2U9RkFMU0UsIHdhcm4uY29uZmxpY3RzPUZBTFNFLCBjaGFyYWN0ZXIub25seT1UUlVFKQp9CmBgYAoKSWYgeW91IGhhdmVuJ3QgZG9uZSB0aGF0IGFscmVhZHksIGRvd25sb2FkIHRoZSB3b3Jrc2hvcCBtYXRlcmlhbHM6CmBgYHtyIGV2YWwgPSBGQUxTRX0Kc2V0d2QoIi9wYXRoL3RvL2Rpci8iKQpkb3dubG9hZC5maWxlKCIvdXJsL3RvL3dvcmtzaG9wL21hdGVyaWFscyIsICJSX3dvcmtzaG9wLnppcCIpCnVuemlwKCJSX3dvcmtzaG9wLnppcCIpCmBgYAoKCiMgSW50cm9kdWN0aW9uCgpJbnN0cnVjdGlvbnM6CgoqIEFzayAqYW55KiBxdWVzdGlvbiAqYW55KiB0aW1lIGlmICphbnkqdGhpbmcgaXMgdW5jbGVhciEKKiBDb2xsYWJvcmF0aW9uIGlzIGVuY291cmFnZWQuIEFzayBhIGZyaWVuZCEgVGhleSBtaWdodCBkZWFsdCB3aXRoIHRoZSBzYW1lCmlzc3VlIGp1c3QgYSBtaW51dGUgYWdvLgoqIEVhY2ggb2YgeW91IHNob3VsZCBoYXZlIDIgcG9zdC1pdHMuIFVzZSB0aGVtIHRvIGF0dGFjaCB0byB5b3UgbGFwdG9wLgogICAgKyA8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5yZWQ8L3NwYW4+IC0tIGlmIHlvdSBjYW4ndCBmaWd1cmUgb3V0IGEgdGFzawogICAgKyA8c3BhbiBzdHlsZT0iY29sb3I6Z3JlZW4iPmdyZWVuPC9zcGFuPiAtLSBldmVyeXRoaW5nIGlzIGdvb2QKCgpUaGlzIGNvdXJzZSBhc3N1bWVzIHRoYXQgeW91OgoKKiBoYXZlIHNvbWUgZXhwZXJpZW5jZSB3aXRoIFIgYWxyZWFkeSAoZS5nLiB0aGUgcHJldmlvdXMgMiB3b3Jrc2hvcCBzZWN0aW9ucykKKiB3b3VsZCBsaWtlIHRvIGxlYXJuIGhvdyB0byBjcmVhdGUgY29vbCB2aXN1YWxpemF0aW9uIFIgd2l0aG91dCAKZ29pbmcgbXVjaCBpbnRvIGRldGFpbCBvZiB3aGF0IGNvbXB1dGF0aW9ucyBhcmUgZG9uZSBvbiB0aGUgc2lkZS4KCgojIFdoYXQgaXMgYGdncGxvdDJgPwoKPiBgZ2dwbG90MmAgaXMgYSBwbG90dGluZyBzeXN0ZW0gZm9yIFIsIGJhc2VkIG9uIHRoZSBncmFtbWFyIG9mIGdyYXBoaWNzLiBJdCB0YWtlcyBjYXJlIG9mIG1hbnkgb2YgdGhlIGZpZGRseSBkZXRhaWxzIHRoYXQgbWFrZSBwbG90dGluZyBhIGhhc3NsZSAobGlrZSBkcmF3aW5nIGxlZ2VuZHMpIGFzIHdlbGwgYXMgcHJvdmlkaW5nIGEgcG93ZXJmdWwgbW9kZWwgb2YgZ3JhcGhpY3MgdGhhdCBtYWtlcyBpdCBlYXN5IHRvIHByb2R1Y2UgY29tcGxleCBtdWx0aS1sYXllcmVkIGdyYXBoaWNzLiBeW2h0dHA6Ly9nZ3Bsb3QyLm9yZy9dCgpBZHZhbnRhZ2VzIG9mIGBnZ3Bsb3QyYDoKCiogcGxvdHMgYXJlIGRlZmluZWQgYXQgYSBoaWdoIGxldmVsIG9mIGFic3RyYWN0aW9uLAoqIHBsb3RzIGFyZSBicm9rZW4gZG93biBpbnRvIG1vZHVsZXMvbGF5ZXJzCiogYSBncmVhdCBmbGV4aWJpbGl0eSB3aGVuICpjdXN0b21pemluZyogeW91ciBwbG90CiogZ29vZCBkb2N1bWVudGF0aW9uIAoqIGEgbGFyZ2UgdXNlciBiYXNlIChlYXN5IGFjY2VzcyB0byBoZWxwKQoKV2Vha25lc3NlcyBvZiBgZ2dwbG90MmAgKHdoYXQgdGhlIHBhY2thZ2Ugc2hvdWxkIG5vdCBiZSB1c2VkIGZvcik6CgoqIDNEIGdyYXBoaWNzOiBzZWUgW2ByZ2xgXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvcmdsL3ZpZ25ldHRlcy9yZ2wuaHRtbCkgCnBhY2thZ2UgaW5zdGVhZCBvciBbYGdncGxvdDJgICsgYHBsb3RseWBdKGh0dHA6Ly9ibG9nLnJldm9sdXRpb25hbmFseXRpY3MuY29tLzIwMTQvMTEvMy1kLXBsb3RzLXdpdGgtcGxvdGx5Lmh0bWwpCiogZ3JhcGgvbmV0d29yayBwbG90cyB3aXRoIG5vZGVzIGFuZCBlZGdlczogc2VlIFtgaWdyYXBoYF0oaHR0cDovL2lncmFwaC5vcmcvci8pIHBhY2thZ2UgaW5zdGVhZAoqIGludGVyYWN0aXZlIGdyYXBoaWNzOiBzZWUgW2BnZ3Zpc2BdKGh0dHA6Ly9nZ3Zpcy5yc3R1ZGlvLmNvbS9nZ3Zpcy1iYXNpY3MuaHRtbCksIApbYHBsb3RseWBdKGh0dHBzOi8vZ2l0aHViLmNvbS9yb3BlbnNjaS9wbG90bHkpIHBhY2thZ2VzIGluc3RlYWQKCgojIFdoYXQgaXMgdGhlIGdyYW1tYXIgb2YgZ3JhcGhpY3M/CgpJdCBpcyBhIGNvbmNlcHQgY29pbmVkIGJ5IExlbGFuZCBXaWxraW5zb24gaW4gKjIwMDUqLgoKKipUaGUgYmFzaWMgaWRlYToqKiBhIHBsb3QgaXMgZGVmaW5lZCBieSBpbmRlcGVuZGVudCBidWlsZGluZyBibG9ja3MsIHdoaWNoCmNvbWJpbmVkIGNyZWF0ZSBqdXN0IGFib3V0IGFueSBraW5kIG9mIHZpc3VhbGl6YXRpb24geW91IHdhbnQuIAoKVGhlIGJ1aWxkaW5nIGJsb2NrcyBvZiBhIGdyYXBoIGluY2x1ZGU6CgoqIGRhdGEKKiBhZXN0aGV0aWMgbWFwcGluZwoqIGdlb21ldHJpYyBvYmplY3RzCiogc3RhdGlzdGljYWwgdHJhbnNmb3JtYXRpb25zCiogc2NhbGVzCiogY29vcmRpbmF0ZSBzeXN0ZW0KKiBwb3NpdGlvbmluZyBhZGp1c3RtZW50cwoqIGZhY3RlaW5nCgojIFRoZSBzdHJ1Y3R1cmUgb2YgZ2dwbG90IG9iamVjdAoKVGhlIGBnZ3Bsb3QoKWAgZnVuY3Rpb24gaXMgdXNlZCB0byBpbml0aWFsaXplIHRoZSBiYXNpYyBncmFwaCBzdHJ1Y3R1cmUuCkl0IGNhbm5vdCBwcm9kdWNlIHRoZSBwbG90IHdlIHdhbnQgYnkgaXRzZWxmLiBJbnN0ZWFkLCB3ZSBuZWVkIHRvIGFkZCBleHRyYSAKYnVpbGRpbmcgYmxvY2tzIGl0LiBUaGUgc3RydWN0dXJlIG9mIGEgZ2dwbG90IGxvb2tzIGxpa2UgdGhpczoKCmBgYHtyLCBldmFsID0gRkFMU0V9CmdncGxvdChkYXRhID0gPGRlZmF1bHQgZGF0YSBzZXQ+LCAKICAgICAgIGFlcyh4ID0gPGRlZmF1bHQgeCBheGlzIHZhcmlhYmxlPiwKICAgICAgICAgICB5ID0gPGRlZmF1bHQgeSBheGlzIHZhcmlhYmxlPiwKICAgICAgICAgICAuLi4gPG90aGVyIGRlZmF1bHQgYWVzdGhldGljIG1hcHBpbmdzPiksCiAgICAgICAuLi4gPG90aGVyIHBsb3QgZGVmYXVsdHM+KSArCiAgCiAgZ2VvbV88Z2VvbSB0eXBlPihhZXMoc2l6ZSA9IDxzaXplIHZhcmlhYmxlIGZvciB0aGlzIGdlb20+LCAKICAgICAgICAgICAgICAgICAgICAgICAuLi4gPG90aGVyIGFlc3RoZXRpYyBtYXBwaW5ncz4pLAogICAgICAgICAgICAgICAgICAgZGF0YSA9IDxkYXRhIGZvciB0aGlzIHBvaW50IGdlb20+LAogICAgICAgICAgICAgICAgICAgc3RhdCA9IDxzdGF0aXN0aWMgc3RyaW5nIG9yIGZ1bmN0aW9uPiwKICAgICAgICAgICAgICAgICAgIHBvc2l0aW9uID0gPHBvc2l0aW9uIHN0cmluZyBvciBmdW5jdGlvbj4sCiAgICAgICAgICAgICAgICAgICBjb2xvciA9IDwiZml4ZWQgY29sb3Igc3BlY2lmaWNhdGlvbiI+LAogICAgICAgICAgICAgICAgICAuLi4gPG90aGVyIGFyZ3VtZW50cywgcG9zc2libHkgcGFzc2VkIHRvIHRoZSBfc3RhdF8gZnVuY3Rpb24pICsKCiAgc2NhbGVfPGFlc3RoZXRpYz5fPHR5cGU+KG5hbWUgPSA8InNjYWxlIGxhYmVsIj4sCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IDx3aGVyZSB0byBwdXQgdGljayBtYXJrcz4sCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IDxsYWJlbHMgZm9yIHRpY2sgbWFya3M+LAogICAgICAgICAgICAgICAgICAgICAgICAgICAuLi4gPG90aGVyIG9wdGlvbnMgZm9yIHRoZSBzY2FsZT4pICsKCiAgdGhlbWUocGxvdC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSAiZ3JheSIpLAogICAgICAgIC4uLiA8b3RoZXIgdGhlbWUgZWxlbWVudHM+KQpgYGAKClRoaXMgY2h1bmsgb2YgY29kZSBtaWdodCBzZWVtIGNvbmZ1c2luZywgYnV0IGJ5IHRoZSBlbmQgb2YgdGhpcyB3b3Jrc2hvcAp5b3Ugc2hvdWxkIGJlIGFibGUgdG8gdW5kZXJzdGFuZCBlYWNoIG9mIHRoZSBjb21wb25lbnRzLgoKKipUaGUgYmFzaWMgaWRlYSoqIGlzIHRoYXQgeW91ICpzcGVjaWZ5IGRpZmZlcmVudCBwYXJ0cyogb2YgdGhlIHBsb3QsIAphbmQgKmFkZCB0aGVtIHRvZ2V0aGVyKiB1c2luZyB0aGUgKyBvcGVyYXRvci4KCgojIGBnZ3Bsb3QyYCB2cyBiYXNlIGdyYXBoaWNzOgoKYGdncGxvdDJgIGNvbXBhcmVkIHRvIGJhc2UgZ3JhcGhpY3MgaXM6CgoqIG1vcmUgdmVyYm9zZSBmb3Igc2ltcGxlIC8gKm91dCBvZiB0aGUgYm94KiBncmFwaGljcwoqIGxlc3MgdmVyYm9zZSBmb3IgY29tcGxleCAvIGN1c3RvbSBncmFwaGljcwoqIHVzZXMgYSBkaWZmZXJlbnQgc3lzdGVtIGZvciBhZGRpbmcgcGxvdCBlbGVtZW50cwooYCtgIGFkZGluZyBvcGVyYXRvciBpbnN0ZWFkIG9mIGNhbGxpbmcgbmV3IGZ1bmN0aW9ucyBsaWtlIApwb2ludHMoKSwgbGluZXMoKSBldGMuKQoKIyMgRXhhbXBsZSAxOiBgSGlzdG9yeSBvZiB1bmVtcGxveWVtbnRgCgpgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTZ9CmRhdGEoImVjb25vbWljcyIpCnN0cihlY29ub21pY3MpCmBgYAoKYGBge3J9CmhlYWQoZWNvbm9taWNzKQpgYGAKCkJhc2UgZ3JhcGhpY3Mgd2l0aCBhIHNpbXBsZSBgcGxvdCgpYCBmdW5jdGlvbjoKCmBgYHtyfQpwbG90KHVuZW1wbG95L3BvcCB+IGRhdGUsIGRhdGEgPSBlY29ub21pY3MsICB0eXBlID0gImwiKQpgYGAKCkEgc2ltaWxhciBwbG90IHVzaW5nIGBnZ3Bsb3QyYAoKYGBge3J9CmdncGxvdChkYXRhID0gZWNvbm9taWNzLCBhZXMoeCA9IGRhdGUsIHkgPSB1bmVtcGxveS9wb3ApKSArIGdlb21fbGluZSgpCmBgYAoKTm90ZSB0aGF0LCBgZ2dwbG90KClgIGJ5IGl0c2VsZiBkb2VzIG5vdCBwbG90IHRoZSBkYXRhLiAKYGBge3J9CmdncGxvdChkYXRhID0gZWNvbm9taWNzLCBhZXMoeCA9IGRhdGUsIHkgPSB1bmVtcGxveS9wb3ApKQpgYGAKWW91IG5lZWQgdG8gYWRkIHRoZSAqbGluZXMqIG9iamVjdC4KCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IGVjb25vbWljcywgYWVzKHggPSBkYXRlLCB5ID0gdW5lbXBsb3kvcG9wKSkgKyBnZW9tX2xpbmUoKQpgYGAKCi4uLiBhbmQgcG9zc2libHkgY2hhbmdlIHRoZSBiYWNrZ3JvdW5kIGNvbG9yIGZyb20gCjxzcGFuIHN0eWxlPSJjb2xvcjpncmF5Ij5kZWZhdWx0IGdyYXk8L3NwYW4+IHRvIAo8c3BhbiBzdHlsZT0iY29sb3I6IHdoaXRlOyBiYWNrZ3JvdW5kOmdyYXkiPmN1c3RvbWl6ZWQgd2hpdGU8L3NwYW4+LiAKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IGVjb25vbWljcywgYWVzKHggPSBkYXRlLCB5ID0gdW5lbXBsb3kvcG9wKSkgKyBnZW9tX2xpbmUoKSArCiAgdGhlbWVfYncoKQpgYGAKCiMjIFdoYXQgaWYgd2Ugd2FudCB0byBjb21wYXJlIHRoZSB0cmVuZCBmcm9tIHllYXIgMjAwOSB0byAyMDE0PwoKQWRkIHR3byB2YXJpYWJsZXMgb25lIGZvciB5ZWFyIGFuZCBvbmUgZm9yIGRheSBvZiB0aGUgeWVhcjogCmBgYHtyfQplY29ub21pY3MkZGF5T2Z0aGVZZWFyIDwtIGZvcm1hdChlY29ub21pY3MkZGF0ZSwgZm9ybWF0PSIlbS0lZCIpCmVjb25vbWljcyRkYXlPZnRoZVllYXIgPC0gYXMuRGF0ZShlY29ub21pY3MkZGF5T2Z0aGVZZWFyLCBmb3JtYXQ9IiVtLSVkIikKZWNvbm9taWNzJHllYXIgPC0gZm9ybWF0KGVjb25vbWljcyRkYXRlLCBmb3JtYXQ9IiVZIikKaGVhZChlY29ub21pY3MpCmBgYAoKVXNpbmcgYmFzZSBncmFwaGljczoKCmBgYHtyfQpwbG90KHVuZW1wbG95L3BvcCB+IGRheU9mdGhlWWVhciwgZGF0YSA9IHN1YnNldChlY29ub21pY3MsIHllYXIgPT0gMjAwOSksIAogICAgIHlsaW0gPSBjKDAuMDI1LCAwLjA1KSwgdHlwZSA9ICJsIikKbGluZXModW5lbXBsb3kvcG9wIH4gZGF5T2Z0aGVZZWFyLCBjb2wgPSAicmVkIiwgZGF0YSA9IHN1YnNldChlY29ub21pY3MsIHllYXIgPT0gMjAxNCkpCmxlZ2VuZCgidG9wbGVmdCIsCiAgICAgICBjKCIyMDA4IiwgIjIwMTQiKSwgdGl0bGU9IlllYXIiLAogICAgICAgY29sPWMoImJsYWNrIiwgInJlZCIpLAogICAgICAgcGNoPWMoMSwgMSkpCmBgYAoKVXNpbmcgYGdncGxvdDJgOgoKYGBge3J9CmdncGxvdChkYXRhID0gc3Vic2V0KGVjb25vbWljcywgeWVhciAlaW4lIGMoMjAxNCwgMjAwOSkpLCAKICAgICAgIGFlcyh4ID0gZGF5T2Z0aGVZZWFyLCB5ID0gdW5lbXBsb3kvcG9wKSkgKyAKICBnZW9tX2xpbmUoYWVzKGNvbG9yID0geWVhcikpIApgYGAKCk5vdGUgdGhhdCB0aGVyZSBpcyBubyBuZWVkIHRvIHNwZWNpZnkgdGhlIGxlZ2VuZCEgSXQgaXMgcHJvZHVjZWQgYXV0b21hdGljYWxseQppbiBgZ2dwbG90MmAuCgpJdCBpcyBlYXN5IHRvIGV2ZW4gcGxvdCBhbGwgdGhlIHllYXJzIHRvZ2V0aGVyOgoKYGBge3IgZmlnLmhlaWdodD01fQpnZ3Bsb3QoZGF0YSA9IGVjb25vbWljcywgYWVzKHggPSBkYXlPZnRoZVllYXIsIHkgPSB1bmVtcGxveS9wb3ApKSArIAogIGdlb21fbGluZShhZXMoY29sb3IgPSB5ZWFyKSkKYGBgCgojIyBFeGFtcGxlIDI6IGRpYW1vbmRzIGRhdGFzZXQKCldlIHdpbGwgbm93IGxvYWQgYSBgZGlhbW9uZHNgIGRhdGEgc2V0IHRoYXQgaXMgaW5jbHVkZWQgaW4gd2l0aCB0aGUgYGdncGxvdDJgIApwYWNrYWdlLiAKClRoZSBkYXRhIHNldCBjb250YWlucyB0aGUgcHJpY2VzIGFuZCBvdGhlciBhdHRyaWJ1dGVzIG9mIGFsbW9zdCA1NCwwMDAgCmRpYW1vbmRzLiBZb3UgY2FuIGNhbGwgYD9kaWFtb25kc2AgdG8gbGVhcm4gbW9yZSBhYm91dCB0aGUgYXZhaWxhYmxlIGF0dHJpYnV0ZXMuCgpgYGB7cn0KZGF0YSgiZGlhbW9uZHMiKQpzdHIoZGlhbW9uZHMpCmBgYAoKYGBge3J9CmhlYWQoZGlhbW9uZHMpCmBgYAoKSXQgaXMgZWFzeSB0byBwbG90IHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIGRpYW1vbmRzIHByaWNlcyB3aXRoIGJhc2UgZ3JhcGhpY3MKCmBgYHtyfQpoaXN0KGRpYW1vbmRzJHByaWNlKQpgYGAKCmFzIHdlbGwgYXMgd2l0aCBgZ2dwbG90MmAKCmBgYHtyfQpnZ3Bsb3QoZGlhbW9uZHMsIGFlcyh4ID0gcHJpY2UpKSArIGdlb21faGlzdG9ncmFtKCkKYGBgCgpOb3cgd2Ugc3Vic2V0IHRoZSBkYXRhIHRvIHNob3cgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRoZSBkaWFtb25kcwp3ZWlnaHRzIChjYXJhdCA9IDIwMCBtZykgYW5kIHRoZWlyIHByaWNlcyAoJCk6IAoKYGBge3J9CnNldC5zZWVkKDEyMzQ1KSAjIE1ha2UgdGhlIHNhbXBsZSByZXByb2R1Y2libGUKZHNtYWxsIDwtIGRpYW1vbmRzW3NhbXBsZShucm93KGRpYW1vbmRzKSwgMjAwKSwgXQpgYGAKCkJhc2UgZ3JhcGhpY3M6CgpgYGB7cn0KY29sb3JNYXAgPC0gZGF0YS5mcmFtZShjb2xvciA9IHJhaW5ib3cobGVuZ3RoKHVuaXF1ZShkc21hbGwkY29sb3IpKSkpCnJvd25hbWVzKGNvbG9yTWFwKSA8LSB1bmlxdWUoZHNtYWxsJGNvbG9yKQoKcGxvdChwcmljZSB+IGNhcmF0LCBkYXRhID0gZHNtYWxsLCBjb2wgPSBjb2xvck1hcFtkc21hbGwkY29sb3IsICJjb2xvciJdKQpsZWdlbmQoeCA9ICdib3R0b21yaWdodCcsIAogICAgICAgbGVnZW5kID0gcm93bmFtZXMoY29sb3JNYXApLAogICAgICAgY29sID0gY29sb3JNYXAkY29sb3IsIHBjaCA9IHBhcigicGNoIiksIGJ0eSA9ICduJywgeGp1c3QgPSAxKQpgYGAKCmBnZ3Bsb3QyYDoKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IGRzbWFsbCwgYWVzKHggPSBjYXJhdCwgeSA9IHByaWNlLCBjb2xvciA9IGNvbG9yKSkgKyBnZW9tX3BvaW50KCkKYGBgCgoKIyBHZW9tZXRyaWMgb2JqZWN0cyBhbmQgQWVzdGhldGljcwoKIyMgR2VvbWV0aWMgT2JqZWN0cyAoYGdlb21gKToKCkdlb21ldHJpYyBvYmplY3RzIGFyZSB0aGUgYWN0dWFsIGl0ZW1zIHdlIHB1dCBvbiBhIHBsb3QuIEV4YW1wbGVzIGluY2x1ZGU6CgoqIHBvaW50cyAoZ2VvbV9wb2ludCwgZm9yIHNjYXR0ZXIgcGxvdHMsIGRvdCBwbG90cywgZXRjKQoqIGxpbmVzIChnZW9tX2xpbmUsIGZvciB0aW1lIHNlcmllcywgdHJlbmQgbGluZXMsIGV0YykKKiBib3hwbG90IChnZW9tX2JveHBsb3QsIGZvciwgd2VsbCwgYm94cGxvdHMhKQoKQSBwbG90IG11c3QgaGF2ZSBhdCBsZWFzdCBvbmUgZ2VvbTsgdGhlcmUgaXMgbm8gdXBwZXIgbGltaXQuIApZb3UgY2FuIGFkZCBhIGdlb20gdG8gYSBwbG90IHVzaW5nIHRoZSBgK2Agb3BlcmF0b3IuCgpZb3UgY2FuIGdldCBhIGxpc3Qgb2YgYXZhaWxhYmxlIGdlb21ldHJpYyBvYmplY3RzIHVzaW5nIHRoZSBjb2RlIGJlbG93OgoKYGBge3J9CmhlbHAuc2VhcmNoKCJnZW9tXyIsIHBhY2thZ2UgPSAiZ2dwbG90MiIpCmBgYAoKb3Igc2ltcGx5IHR5cGUgYGdlb21fPHRhYj5gIGluIGFueSBnb29kIFIgSURFIChzdWNoIGFzIFJzdHVkaW8gb3IgRVNTKSB0byBzZWUKYSBsaXN0IG9mIGZ1bmN0aW9ucyBzdGFydGluZyB3aXRoIGBnZW9tX2AuCgoKIyMgQWVzdGhldGljIE1hcHBpbmcKCj4gSW4gZ2dwbG90IGFuICphZXN0aGV0aWMgbWFwcGluZyosIGRlZmluZWQgd2l0aCBhZXMoKSwgZGVzY3JpYmVzIGhvdyB2YXJpYWJsZXMgCmFyZSBtYXBwZWQgdG8gdmlzdWFsIHByb3BlcnRpZXMgb3IgYWVzdGhldGljcy4KCkV4YW1wbGVzIG9mIGFlc3RoZXRpY3MgYXJlOiAKCiogcG9zaXRpb24gKGkuZS4sIG9uIHRoZSB4IGFuZCB5IGF4ZXMpCiogY29sb3IgKCJvdXRzaWRlIiBjb2xvcikKKiBmaWxsICgiaW5zaWRlIiBjb2xvcikKKiBzaGFwZSAob2YgcG9pbnRzKQoqIGxpbmV0eXBlCiogc2l6ZQoKRWFjaCB0eXBlIG9mIGdlb20gYWNjZXB0cyBvbmx5IGEgc3Vic2V0IG9mIGFsbCBhZXN0aGV0aWNzLiBSZWZlciB0bwp0aGUgZ2VvbSBoZWxwIHBhZ2VzIHRvIHNlZSB3aGF0IG1hcHBpbmdzIGVhY2ggZ2VvbSBhY2NlcHRzLiAKCiMjIFNjYXR0ZXIgcGxvdHMgKGBnZW9tX3BvaW50c2ApCgoKYGBge3J9CnAxIDwtIGdncGxvdChkc21hbGwsIGFlcyh4ID0gY2FyYXQsIHkgPSBwcmljZSkpCnAxICsgZ2VvbV9wb2ludCgpCmBgYAoKYGBge3J9CnAxICsgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBjb2xvcikpCmBgYAoKYGBge3J9CnAxICsgZ2VvbV9wb2ludChhZXMoc2hhcGUgPSBjdXQpKQpgYGAKCmBgYHtyfQpwMSArIGdlb21fcG9pbnQoYWVzKHNoYXBlID0gY3V0LCBjb2xvciA9IGNvbG9yKSkKYGBgCgojIyBBZXN0aGV0aWMgTWFwcGluZyB2cyBBc3NpZ25tZW50CgpOb3RlIHRoYXQgdmFyaWFibGVzIGFyZSBtYXBwZWQgdG8gYWVzdGhldGljcyB3aXRoIHRoZSBgYWVzKClgIGZ1bmN0aW9uLCAKd2hpbGUgZml4ZWQgYWVzdGhldGljcyBhcmUgc2V0IG91dHNpZGUgdGhlIGBhZXMoKWAgY2FsbC4gCgpUaGlzIHNvbWV0aW1lcyBsZWFkcyB0byBjb25mdXNpb24sIGFzIGluIHRoaXMgZXhhbXBsZToKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IGRzbWFsbCwgYWVzKHggPSBjYXJhdCwgeSA9IHByaWNlKSkgKyAKICBnZW9tX3BvaW50KGFlcyhzaXplID0gMiksICMgdGhpcyBpcyBjb25jZXB0dWFsbHkgd3Jvbmcgc2luY2UgMiBpcyBub3QgYSB2YXJpYWJsZQogICAgICAgICAgICAgY29sb3IgPSAiZGFya2dyZWVuIikgIyB0aGlzIGlzIG9rIHNpbmNlIHlvdSBtaWdodCB3YW50IGFsbCBwb2ludHMgdG8gYmUgZ3JlZW4uCmBgYAoKYGBge3J9CmdncGxvdChkYXRhID0gZHNtYWxsLCBhZXMoeCA9IGNhcmF0LCB5ID0gcHJpY2UpKSArIAogIGdlb21fcG9pbnQoYWVzKGZpbGwgPSBjdXQpLCBzaXplID0gMiwgY29sb3IgPSAiYmxhY2siLCBzaGFwZSA9IDI1KQpgYGAKCgojIyBBdmFpbGFibGUgc2hhcGUgY29uZmlndXJhdGlvbnMKCmBgYHtyfQojIyBBIGxvb2sgYXQgYWxsIDI1IHN5bWJvbHMKZGYyIDwtIGRhdGEuZnJhbWUoeCA9IDE6NSAsIHkgPSAxOjI1LCB6ID0gMToyNSkKcyA8LSBnZ3Bsb3QoZGYyLCBhZXMoeCA9IHgsIHkgPSB5KSkKcyArIGdlb21fcG9pbnQoYWVzKHNoYXBlID0geiksIHNpemUgPSAzLCBjb2xvdXIgPSAiYmx1ZSIpICsKICBzY2FsZV9zaGFwZV9pZGVudGl0eSgpCmBgYAoKCmBgYHtyfQojIyBXaGlsZSBhbGwgc3ltYm9scyBoYXZlIGEgZm9yZWdyb3VuZCBjb2xvdXIsIHN5bWJvbHMgMTktMjUgYWxzbyB0YWtlIGEKIyMgYmFja2dyb3VuZCBjb2xvdXIgKGZpbGwpCnMgKyBnZW9tX3BvaW50KGFlcyhzaGFwZSA9IHopLCBzaXplID0gMywgY29sb3VyID0gImRhcmtncmVlbiIsIGZpbGwgPSAib3JhbmdlIikgKwogIHNjYWxlX3NoYXBlX2lkZW50aXR5KCkKYGBgCgoKIyMgRGF0YSB0cmFuc2Zvcm1hdGlvbnMKCldlIGNhbiBwbG90IHRoZSB0cmFuc2Zvcm1lZCBkYXRhIGJ5IGNhbGxpbmcgYSBmdW5jdGlvbiBvbiB0aGUgdmFyaWFibGUuCkZvciBleGFtcGxlIGhlcmUgd2Ugc2hvdyB0aGUgbG9nIHRyYW5zZm9ybWVkIGRhdGEuCgpgYGB7cn0KZ2dwbG90KGRzbWFsbCwgYWVzKHggPSBsb2coY2FyYXQpLCB5ID0gbG9nKHByaWNlKSkpICsgZ2VvbV9wb2ludCgpCmBgYAoKIyMgVGV4dCBsYWJlbHMKClVzZSBldmVuIGEgc21hbGxlciBzdWJzZXQgdG8gYXZvaWQgY2x1dHRlcmluZzoKYGBge3J9CnNldC5zZWVkKDEyMzQ1KSAjIE1ha2UgdGhlIHNhbXBsZSByZXByb2R1Y2libGUKZHNtYWxsMiA8LSBkaWFtb25kc1tzYW1wbGUobnJvdyhkaWFtb25kcyksIDEwMCksIF0KYGBgCgpgYGB7cn0KcDIgPC0gZ2dwbG90KGRzbWFsbDIsIGFlcyh4ID0gbG9nKGNhcmF0KSwgeSA9IGxvZyhwcmljZSkpKQpwMiArIGdlb21fdGV4dChhZXMobGFiZWwgPSBjb2xvcikpCmBgYAoKYGBge3J9CnAyICsgZ2VvbV9sYWJlbChhZXMobGFiZWwgPSBjb2xvcikpCmBgYAoKVGhlIGBnZ3JlcGxlbGAgZ2l2ZXMgYW4gZWFzeSB3YXkgdG8gYW5ub3RhdGUgdGhlIGxhYmVscyB3aGVuIHRoZXkgYXJlIGRlbnNlbHkKcGFja2VkLiAKCmBgYHtyfQpsaWJyYXJ5KGdncmVwZWwpCnAyICsgZ2VvbV9wb2ludCgpICsgZ2VvbV90ZXh0X3JlcGVsKGFlcyhsYWJlbD1jb2xvciksIHNpemUgPSAzKQpgYGAKCkl0IGRvZXNuJ3Qgd29yayB0aGF0IHdlbGwgdGhvdWdoIGlmIHlvdSBoYXZlIHRvbyBtYW55IHBvaW50cyBjbHVzdGVyZWQKdG9nZXRoZXIsIHRoZW4gdGhlIGxpbmVzIHBvaW50aW5nIHRvIHRoZSBwb2ludHMgd2lsbCBleHRlbmQgdG9vIGZhciB3YXksCnRvIG1ha2Ugcm9vbSBmb3IgYWxsIHRoZSBsYWJlbHMuIAoKCmBgYHtyfQpwMSArIGdlb21fcG9pbnQoKSArIGdlb21fdGV4dF9yZXBlbChhZXMobGFiZWw9Y29sb3IpLCBzaXplID0gMykKYGBgCgpJbiB0aGVzZSBjYXNlcyB5b3Ugc2hvdWxkIGNob29zZSB0byBsYWJlbApvbmx5IGEgc3Vic2V0IG9mIHBvaW50cy4KCmBgYHtyfQpzZXQuc2VlZCgxMjM0NTYpCnN1YnNldERhdGEgPC0gc3Vic2V0KGRzbWFsbCwgc2FtcGxlKGMoVFJVRSwgRkFMU0UpLCBucm93KGRzbWFsbCksIHJlcGxhY2UgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcm9iID0gYygwLjIsIDAuOCkpKQpwMSArIGdlb21fcG9pbnQoKSArIAogIGdlb21fdGV4dF9yZXBlbChkYXRhID0gc3Vic2V0RGF0YSwgYWVzKGxhYmVsPWNvbG9yKSwgc2l6ZSA9IDUsIGNvbCA9ICJCbHVlIikKYGBgCgojIFRoZSBFY29ub21pc3QgRGF0YQoKRm9yIHByYWN0aWNlLCB5b3Ugd2lsbCB0cnkgdG8gcmVjcmVhdGUKYSBwbG90IHB1Ymxpc2hlZCBpbiB0aGUgRWNvbm9taXN0IGlzc3VlIG9mIEp1bHkgMjB0aCwgMjAxNiByZWZsZWN0aW5nCnRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB3ZWxsLWJlaW5nIGFuZCBmaW5hbmNpYWwgaW5jbHVzaW9uLgoKIVtdKC4vZmlndXJlcy9lY29ub21pc3QucG5nKQoKR3JhcGggc291cmNlOiBbaHR0cDovL3d3dy5lY29ub21pc3QuY29tL2Jsb2dzL2dyYXBoaWNkZXRhaWwvMjAxNi8wNy9kYWlseS1jaGFydC0xM10oaHR0cDovL3d3dy5lY29ub21pc3QuY29tL2Jsb2dzL2dyYXBoaWNkZXRhaWwvMjAxNi8wNy9kYWlseS1jaGFydC0xMykKCllvdSB3aWxsIGdlbmVyYXRlIHRoaXMgZmlndXJlIHN0ZXAgYnkgc3RlcCB0aHJvdWdoIGEgc2VyaWVzIG9mIGluY2x1ZGVkIApleGVyY2lzZXMgdXNpbmcgdGhlIHRvb2xzIHdlJ3ZlIGp1c3QgbGVhcm5lZCBhbmQgd2lsbCBsZWFybiBhYm91dC4gCgpUaGUgZGF0YSBmb3IgdGhlIGV4ZXJjaXNlcyBpcyBhdmFpbGFibGUgaW4gdGhlIGBkYXRhU2V0cy9FY29ub21pc3REYXRhLmNzdmAgZmlsZS4gClJlYWQgaXQgaW4gd2l0aCB0aGUgZm9sbG93aW5nIGNvbW1hbmRzOgoKYGBge3J9CmRhdCA8LSByZWFkLmNzdigiLi9kYXRhL0Vjb25vbWlzdERhdGEuY3N2IikKaGVhZChkYXQpCmBgYAoKClRoZSBvcmlnaW5hbCBzb3VyY2VzIGZvciB0aGlzIGRhdGEgYXJlOgoKKiB0aGUgQm9zdG9uIENvbnN1bHRpbmcgR3JvdXDigJlzIFtyZXBvcnQgb24gY291bnRyaWVzJyB3ZWxsLWJlaW5nXShodHRwczovL3d3dy5iY2dwZXJzcGVjdGl2ZXMuY29tL0ltYWdlcy9CQ0ctVGhlLVByaXZhdGUtU2VjdG9yLU9wcG9ydHVuaXR5LXRvLUltcHJvdmUtV2VsbC1CZWluZy1KdWwtMjAxNi5wZGYpLAp3aGljaCBpbmNsdWRlcyBTdXN0YWluYWJsZSBFY29ub21pYyBEZXZlbG9wbWVudCBBc3Nlc3NtZW50IChTRURBKSBzY29yZXMsIAoqcG93ZXJmdWwgZGlhZ25vc3RpY3MgZGVzaWduZWQgdG8gcHJvdmlkZSBnb3Zlcm5tZW50IGxlYWRlcnMgd2l0aCBhIHBlcnNwZWN0aXZlIG9uIGhvdyBlZmZlY3RpdmVseSB0aGVpciBjb3VudHJpZXMgY29udmVydCB3ZWFsdGgsIGFzIG1lYXN1cmVkIGJ5IGluY29tZSBsZXZlbHMsIGludG8gd2VsbC1iZWluZypeW2h0dHBzOi8vd3d3LmJjZ3BlcnNwZWN0aXZlcy5jb20vY29udGVudC9hcnRpY2xlcy9ncm93dGgtZ2xvYmFsaXphdGlvbi1wcml2YXRlLXNlY3Rvci1vcHBvcnR1bml0eS1pbXByb3ZlLXdlbGwtYmVpbmctMjAxNi1lY29ub21pYy1kZXZlbG9wbWVudC1hc3Nlc3NtZW50L10gCiogdGhlIFdvcmxkIEJhbmsgW0dsb2JhbCBGaW5kZXggZGF0YWJhc2VdKGh0dHA6Ly9kYXRhdG9waWNzLndvcmxkYmFuay5vcmcvZmluYW5jaWFsaW5jbHVzaW9uLyksCndoaWNoIHJlY29yZHMgdGhlIGluZGljZXMgb2YgZmluYW5jaWFsIGluY2x1c2lvbiwgaW5jbHVkaW5nIHRoZSBwZXJjZW50Cm9mIHBlb3BsZSBhZ2VkIDE1IG9yIG1vcmUgd2l0aCBhIGJhbmsgYWNjb3VudC4KCgpUaGUgY291bnRyaWVzIGFzc2lnbm1lbnQgdG8gcmVnaW9ucyBpcyBiYXNlZCBvbiB0aGUgRVBJX3JlZ2lvbnMgY29sdW1uIGluIAp0aGUgYGNvdW50cnlFeERhdGFgIGRhdGEuZnJhbWUgZnJvbSBgcndvcmxkbWFwYCBwYWNrYWdlLiBUaGUgYFJlZ2lvbmAgdmFyaWFibGUgCndhcyBtYXRjaGVkIHdpdGggdGhlIGNhdGVnb3JpZXMgaW4gdGhlIEVjb25vbWlzdCBwbG90LiAKCgojIEV4ZXJjaXNlIEkKCkZvciB0aGUgYEVjb25vbWlzdERhdGEuY3N2YCBkbyB0aGUgZm9sbG93aW5nOgoKMS4gQ3JlYXRlIGEgc2NhdHRlciBwbG90IHdpdGggcGVyY2VudCBvZiBwZW9wbGUgb3ZlciB0aGUgYWdlIG9mIDE1IHdpdGggYSBiYW5rIAphY2NvdW50IG9uIHRoZSB4IGF4aXMgYW5kIHRoZSBTRURBIHNjb3JlIG9uIHRoZSB5IGF4aXMuCjIuIENvbG9yIHRoZSBwb2ludHMgaW4gdGhlIHByZXZpb3VzIHBsb3QgYmx1ZS4KMy4gQ29sb3IgdGhlIHBvaW50cyBpbiB0aGUgcHJldmlvdXMgcGxvdCBhY2NvcmRpbmcgdG8gdGhlIGBSZWdpb25gLgo0LiBDcmVhdGUgYm94cGxvdHMgb2YgU0VEQSBzY29yZXMgYnkgYFJlZ2lvbmAuCjUuIE92ZXJsYXkgcG9pbnRzIG9uIHRvcCBvZiB0aGUgYm94IHBsb3RzCgoKYGBge3J9CiMgKC4uLj8pCmBgYAoKCiMgU3RhdGlzdGljYWwgVHJhbnNmb3JtYXRpb25zCgpOb3csIHdlIHdpbGwgZ28gYmFjayB0byB0aGUgYGRpYW1vbmRzYCBkYXRhIHNldCBhbmQgcmV0dXJuIHRvIAp0aGUgRWNvbm9taXN0IHBsb3QgbGF0ZXIuCgpTbyBmYXIgd2UgaGF2ZSBvbmx5IGRlYWx0IHdpdGggdGhlICh4LHkpIHR5cGUgb2YgcGxvdHMgKHNjYXR0ZXIgcGxvdHMgb3IgCmxpbmUgcGxvdHMpIHdoZXJlIGVhY2ggb2YgdGhlIHBvaW50IGhhcyBpdHMgY29ycmVzcG9uZGluZyAoeCx5KSBjb29yZGluYXRlLgoKU29tZXRpbWVzLCBob3dldmVyLCB3ZSBhcmUgbW9yZSBpbnRlcmVzdGVkIGluIHBsb3RzIHRoYXQgcmVxdWlyZSBzb21lIApzdGF0aXN0aWNhbCB0cmFuc2Zvcm1hdGlvbnMuIFRoZSB0cmFuc2Zvcm1hdGlvbnMgbWlnaHQgbWFwIGEgcmF3IGRhdGFwb2ludCBvciAKYSBncm91cCBvZiBkYXRhcG9pbnRzIHRvIG90aGVyIHZhbHVlcy4gRXhhbXBsZXMgb2YgcGxvdHMgaW52b2x2aW5nIHN0YXRpc3RpY2FsIAp0cmFuc2Zvcm1hdGlvbnM6CgoqIGJveHBsb3RzIHdlIGp1c3QgZ2VuZXJhdGVkIGZvciB0aGUgRWNvbm9taXN0IGRhdGEsCiogaGlzdG9ncmFtcwoqIHByZWRpY3Rpb24gbGluZXMgZXRjLgoqIGJhciBjaGFydHMKClRoZXNlIHR5cGVzIG9mIHBsb3RzIHJlcXVpcmUgc29tZSBzdGF0aXN0aWNhbCB0cmFuc2Zvcm1hdGlvbnMuIEZvciBleGFtcGxlOgoKKiBib3hwbG90cyByZXF1aXJlIGNvbXB1dGF0aW9ucyBvZiB0aGUgdGhlIG1lZGlhbiwgbG93ZXIgYW5kIHVwcGVyIHF1YXJ0aWxlLCAKYW5kIDEuNSAqIElRUiBvZiB0aGUgeS12YWx1ZXMsCiogc21vb3RoZXJzIGNvbXB1dGUgdGhlIHByZWRpY3RlZCB2YWx1ZXMgZm9yIHktdmFsdWVzLAoqIGhpc3RvZ3JhbXMgZ3JvdXAgdGhlIHZhbHVlcyBpbnRvIGJpbnMsIAoqIGJhciBjaGFydHMgY291bnRzIHRoZSBjbGFzc2VzIG9jY3VycmVuY2VzLiAKCiMjIEJveHBsb3RzIGFuZCBqaXR0ZXJlZCBwb2ludHMKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IGRpYW1vbmRzLCBhZXMoeCA9IGNvbG9yLCB5ID1wcmljZS9jYXJhdCkpICsKICBnZW9tX2ppdHRlcigpCmBgYAoKCmBgYHtyIGZpZy5oZWlnaHQ9MywgZmlnLndpZHRoPTd9CmoxIDwtIGdncGxvdChkYXRhID0gZGlhbW9uZHMsIGFlcyh4ID0gY29sb3IsIHkgPXByaWNlL2NhcmF0KSkgKwogIGdlb21faml0dGVyKGFscGhhID0gSSgxLzUpKQoKajIgPC0gZ2dwbG90KGRhdGEgPSBkaWFtb25kcywgYWVzKHggPSBjb2xvciwgeSA9cHJpY2UvY2FyYXQpKSArCiAgZ2VvbV9qaXR0ZXIoYWxwaGEgPSBJKDEvNTApKQoKajMgPC0gZ2dwbG90KGRhdGEgPSBkaWFtb25kcywgYWVzKHggPSBjb2xvciwgeSA9cHJpY2UvY2FyYXQpKSArCiAgZ2VvbV9qaXR0ZXIoYWxwaGEgPSBJKDEvMjAwKSkKCmdyaWQuYXJyYW5nZShqMSwgajIsIGozLCBuY29sID0gMykKYGBgCgpIZXJlIHdlIHVzZWQgYGdyaWQuYXJyYW5nZSgpYCBmb3IgdGhlIHBhY2thZ2UgYGdyaWRFeHRyYWAgdG8gZGlzcGxheSAKbXVsdGlwbGUgcGxvdHMgaW4gdGhlIHNhbWUgbGluZS4KClNvbWV0aW1lcyBsZXNzIGlzIG1vcmUuLi4KCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IGRpYW1vbmRzLCBhZXMoeCA9IGNvbG9yLCB5ID1wcmljZS9jYXJhdCkpICsKICBnZW9tX2JveHBsb3QoKQpgYGAKCiMjIEhpc3RvZ3JhbSBhbmQgZGVuc2l0eSBwbG90cwoKQmVsb3cgd2UgcGxvdCB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSB3ZWlnaHRzIChjYXJhdCkgb2YgdGhlIGRpYW1vbmRzLgoKCmBgYHtyIGZpZy5oZWlnaHQ9MywgZmlnLndpZHRoPTd9CmggPC0gZ2dwbG90KGRhdGEgPSBkaWFtb25kcywgYWVzKHggPSBjYXJhdCkpICsgZ2VvbV9oaXN0b2dyYW0oKQpkIDwtIGdncGxvdChkYXRhID0gZGlhbW9uZHMsIGFlcyh4ID0gY2FyYXQpKSArIGdlb21fZGVuc2l0eSgpCgpncmlkLmFycmFuZ2UoaCwgZCwgbmNvbCA9IDIpCmBgYAoKKiBGb3IgdGhlIGRlbnNpdHkgcGxvdCwgdGhlIGBhZGp1c3RgIGFyZ3VtZW50IGNvbnRyb2xzIHRoZSBkZWdyZWUgb2Ygc21vb3RobmVzcwooaGlnaCB2YWx1ZXMgb2YgYWRqdXN0IHByb2R1Y2Ugc21vb3RoZXIgcGxvdHMpLiAKKiBGb3IgdGhlIGhpc3RvZ3JhbSwgdGhlIGBiaW53aWR0aGAgb3IgYGJpbnNgIGFyZ3VtZW50IGNvbnRyb2xzIHRoZSBhbW91bnQgCm9mIHNtb290aGluZyBieSBzZXR0aW5nIHRoZSBiaW4gc2l6ZSBvciB0aGUgbnVtYmVyIG9mIGJpbnMuIAooQnJlYWsgcG9pbnRzIGNhbiBhbHNvIGJlIHNwZWNpZmllZCBleHBsaWNpdGx5LCB1c2luZyB0aGUgYnJlYWtzIGFyZ3VtZW50LikgCgo8IS0tIEl0IGlzIHZlcnkgaW1wb3J0YW50IHRvIGV4cGVyaW1lbnQgd2l0aCB0aGUgbGV2ZWwgb2Ygc21vb3RoaW5nLiBXaXRoIGEgaGlzdG9ncmFtIC0tPgo8IS0tIHlvdSBzaG91bGQgdHJ5IG1hbnkgYmluIHdpZHRoczogWW91IG1heSBmaW5kIHRoYXQgZ3Jvc3MgZmVhdHVyZXMgb2YgdGhlIGRhdGEgLS0+CjwhLS0gc2hvdyB1cCB3ZWxsIGF0IGEgbGFyZ2UgYmluIHdpZHRoLCB3aGlsZSBmaW5lciBmZWF0dXJlcyByZXF1aXJlIGEgdmVyeSBuYXJyb3cgLS0+CjwhLS0gd2lkdGguIC0tPgoKYGBge3IgZmlnLmhlaWdodD0zLCBmaWcud2lkdGg9N30KcCA8LSBnZ3Bsb3QoZGF0YSA9IGRpYW1vbmRzLCBhZXMoeCA9IGNhcmF0KSkgKyB4bGltKDAsIDMpCgpoMSA8LSBwICsgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAxKSAKaDIgPC0gcCArIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMC4xKSAKaDMgPC0gcCArIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMC4wMSkgCgpncmlkLmFycmFuZ2UoaDEsIGgyLCBoMywgbmNvbCA9IDMpCmBgYAoKYGBge3IgZmlnLmhlaWdodD0zLCBmaWcud2lkdGg9N30KZDEgPC0gcCArIGdlb21fZGVuc2l0eShhZGp1c3QgPSA1KSAKZDIgPC0gcCArIGdlb21fZGVuc2l0eShhZGp1c3QgPSAxKSAKZDMgPC0gcCArIGdlb21fZGVuc2l0eShhZGp1c3QgPSAxLzUpIAoKZ3JpZC5hcnJhbmdlKGQxLCBkMiwgZDMsIG5jb2wgPSAzKQpgYGAKClRoZSBoaXN0b2dyYW1zIGNhbiBiZSBicm9rZW4gZG93biBpbnRvIGdyb3Vwcy4gSGVyZSB3ZSBzaG93IGdyb3VwaW5nIGJ5IGRpYW1vbmRzCmN1dC4KCmBgYHtyIGZpZy5oZWlnaHQ9MywgZmlnLndpZHRoPTd9CmggPC0gcCArIGdlb21faGlzdG9ncmFtKGFlcyhmaWxsID0gY3V0KSwgcG9zaXRpb24gPSAiZG9kZ2UiLCBiaW5zID0gMTApCmQgPC0gcCArIGdlb21fZGVuc2l0eShhZXMoY29sb3IgPSBjdXQpKQoKZ3JpZC5hcnJhbmdlKGgsIGQsIG5jb2wgPSAyKQpgYGAKCkluc3RlYWQgb2YgdGhlIG1hcmdpbmFsIGRpc3RyaWJ1dGlvbiwgd2UgY2FuIHBsb3QgdGhlIGNvbXBvbmVudHMgKipzdGFja2VkKioKb24gdG9wIG9mIGVhY2ggb3RoZXIgdG8gc2VlIHRoZSBjb250cmlidXRpb24gZnJvbSBlYWNoIG9mIGdyb3VwLgoKYGBge3IgZmlnLmhlaWdodD0zLCBmaWcud2lkdGg9N30KaCA8LSBwICsgZ2VvbV9oaXN0b2dyYW0oYWVzKGZpbGwgPSBjdXQpLCBwb3NpdGlvbiA9ICJzdGFjayIpCmQgPC0gcCArIGdlb21fZGVuc2l0eShhZXMoZmlsbCA9IGN1dCksIHBvc2l0aW9uID0gInN0YWNrIikKCmdyaWQuYXJyYW5nZShoLCBkLCBuY29sID0gMikKYGBgCgojIyBCYXIgY2hhcnRzCgpUaGUgZGlzY3JldGUgYW5hbG9ndWUgb2YgaGlzdG9ncmFtIGlzIHRoZSBiYXIgY2hhcnQsIGBnZW9tID0gImJhciJgLiAKSW5zdGVhZCBvZiBwYXJ0aXRpb25pbmcgdGhlIHZhbHVlcyBpbnRvIGJpbnMgbGlrZSBoaXN0b2dyYW1zLCB0aGUgYmFyCmdlb20gY291bnRzIHRoZSBudW1iZXIgb2YgaW5zdGFuY2VzIG9mIGVhY2ggZGlzY3JldGUgY2xhc3MuIFRoZSBjb3VudHMKYXJlIHRoZW4gcGxvdHRlZCBhcyBjb2x1bW5zIGZvciBlYWNoIGRpc3RpbmN0IGNsYXNzLgoKSWYgeW914oCZZCBsaWtlIHRvIHRhYnVsYXRlIGNsYXNzIG1lbWJlcnMgaW4gc29tZSBvdGhlciB3YXkgKHJhdGhlciB0aGFuIGNvdW50KSwgCmUuZy4gYnkgc3VtbWluZyB1cCBhIGNvbnRpbnVvdXMgdmFyaWFibGUsIHlvdSBjYW4gdXNlIHRoZSBgd2VpZ2h0YCBhZXN0aGV0aWMuIAoKYGBge3IgZmlnLmhlaWdodD0zLCBmaWcud2lkdGg9Ni44fQpiMSA8LSBnZ3Bsb3QoZGlhbW9uZHMsIGFlcyh4ID0gY29sb3IpKSArIGdlb21fYmFyKCkKYjIgPC0gZ2dwbG90KGRpYW1vbmRzLCBhZXMoeCA9IGNvbG9yKSkgKyBnZW9tX2JhcihhZXMod2VpZ2h0ID0gY2FyYXQpKSArIHlsYWIoImNhcmF0IikKZ3JpZC5hcnJhbmdlKGIxLCBiMiwgbmNvbCA9IDIpCmBgYAoKVGhlIGxlZnQgcGxvdCBzaG93cyBjb3VudHMgYW5kIHRoZSByaWdodCBwbG90IGlzIHRoZSBjb3VudCB3ZWlnaHRlZCBieSAKYHdlaWdodCA9IGNhcmF0YCB0byBzaG93IHRoZSB0b3RhbCB3ZWlnaHQgb2YgZGlhbW9uZHMgb2YgZWFjaCBjb2xvci4KCgpUaHVzLCB5b3UgZG9u4oCZdCBuZWVkIHRvIHRhYnVsYXRlIHlvdXIgdmFsdWVzIGJlZm9yZWhhbmQsIGFzIHdpdGggYGJhcmNoYXJ0YCAKaW4gYmFzZSBSLiBIb3dldmVyLCBpZiB5b3UgZGlkIGFscmVhZHkgc3VtbWFyaXplIHlvdXIgZGF0YSwgeW91IGNhbiBzdGlsbCB1c2UKYGdlb21fYmFyYCBidXQgdXNpbmcgYW5vdGhlciBzdGF0aXN0aWNhbCB0cmFuc2Zvcm1hdGlvbiBgc3RhdCA9ICJpZGVudGl0eWAKcmF0aGVyIHRoYW4gdGhlIGRlZmF1bHQgYHN0YXQgPSAiY291bnQiYC4KCmBgYHtyfQpkaWFtb25kcy5tZWFuIDwtIGFnZ3JlZ2F0ZShkaWFtb25kc1siY2FyYXQiXSwgZGlhbW9uZHNbImNvbG9yIl0sIEZVTj1tZWFuKQpyYmluZChoZWFkKGRpYW1vbmRzLm1lYW4pLCB0YWlsKGRpYW1vbmRzLm1lYW4pKQpgYGAKClRoZSBkZWZhdWx0IG9wdGlvbiB3aWxsIGdlbmVyYXRlIGFuIGVycm9yOgoKYGBge3J9CmdncGxvdChkaWFtb25kcy5tZWFuLCBhZXMoeD1jb2xvciwgeT1jYXJhdCkpICsgCiAgZ2VvbV9iYXIoKQpgYGAKClRodXMsIHlvdSBuZWVkIHRvIHVzZSB0aGUgZm9sbG93aW5nOgoKYGBge3J9CmdncGxvdChkaWFtb25kcy5tZWFuLCBhZXMoeD1jb2xvciwgeT1jYXJhdCkpICsgCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKQpgYGAKCmBgYHtyfQpkaWFtb25kcy5zdW0gPC0gYWdncmVnYXRlKGRpYW1vbmRzWyJjYXJhdCJdLCBkaWFtb25kc1siY29sb3IiXSwgRlVOPXN1bSkKZ2dwbG90KGRpYW1vbmRzLnN1bSwgYWVzKHg9Y29sb3IsIHk9Y2FyYXQpKSArICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpCmBgYAoKTm90ZSB0aGF0IHRoaXMgaXMgdGhlIHNhbWUgcGxvdCBhcyB0aGUgb25lIGdlbmVyYXRlZCB3aXRoIHRoZSBgd2VpZ2h0YAphZXN0aGV0aWMsIHdoaWNoIGlzIGV4YWN0bHkgd2hhdCB3ZSBzaG91bGQgZXhwZWN0LgoKCiMjIFByZWRpY3Rpb24gbGluZXMKCldlIGNhbiBpbmNsdWRlIGEgcmVncmVzc2lvbiBsaW5lIHRvIHBsb3QgYnkgc2ltcGx5CmFkZGluZyB0aGUgbGluZSB3aXRoIHRoZSBmaXR0ZWQgeS12YWx1ZXMgZnJvbSBhIHByZWRpY3Rpb24gbW9kZWw6CgpgYGB7cn0KZHNtYWxsJHByZWQucHJpY2UgPC0gcHJlZGljdChsbShwcmljZSB+IGNhcmF0LCBkYXRhID0gZHNtYWxsKSkKcDEgPC0gZ2dwbG90KGRzbWFsbCwgYWVzKHggPSBjYXJhdCwgeSA9IHByaWNlKSkKcDEgKyBnZW9tX3BvaW50KGFlcyhjb2xvciA9IGNvbG9yKSkgKyBnZW9tX2xpbmUoYWVzKHkgPSBwcmVkLnByaWNlKSkKYGBgCgoKIyMgU21vb3RoZXJzCgpJZiB5b3UgaGF2ZSBhIHNjYXR0ZXJwbG90IHdpdGggbWFueSBkYXRhIHBvaW50cywgaXQgY2FuIGJlIGhhcmQgdG8gc2VlIGV4YWN0bHkKd2hhdCB0cmVuZCBpcyBzaG93biBieSB0aGUgZGF0YS4gSW4gdGhpcyBjYXNlIHlvdSBtYXkgd2FudCB0byBhZGQgYSBzbW9vdGhlZApsaW5lIHRvIHRoZSBwbG90LiBUaGUgc21vb3RoIGdlb20gaW5jbHVkZXMgYSBsaW5lIGFuZCBhIHJpYmJvbi4KCgpgYGB7cn0KZ2dwbG90KGRhdGEgPSBkaWFtb25kcywgYWVzKHggPSBjYXJhdCwgeSA9IHByaWNlKSkgKwogIGdlb21fcG9pbnQoKSArIGdlb21fc21vb3RoKCkKYGBgCgpGb3Igb3VyIHNtYWxsIHN1YnNldCBvZiB0aGUgZGlhbW9uZHMgZGF0YSBzZXQgd2UgaGF2ZToKCmBgYHtyfQpwMSArIGdlb21fcG9pbnQoKSArIGdlb21fc21vb3RoKCkKYGBgCgpDaGFuZ2luZyB0aGUgYHNwYW5gIGFyZ3VtZW50LCB3ZSBjYW4gb2J0YWluIG1vcmUgb3IgbGVzcyB3aWdnbHkgY3VydmUKKHNtYWxsZXIgYHNwYW5gIHJlc3VsdHMgaW4gbW9yZSB3aWdnbGluZXNzKS4KCmBgYHtyfQpncmlkLmFycmFuZ2UocDEgKyBnZW9tX3BvaW50KCkgKyBnZW9tX3Ntb290aChzcGFuID0gMC4yKSwKICAgICAgICAgICAgIHAxICsgZ2VvbV9wb2ludCgpICsgZ2VvbV9zbW9vdGgoc3BhbiA9IDAuNyksIG5jb2wgPSAyKQpgYGAKCgoqIFRoZSBkZWZhdWx0IG1ldGhvZCB1c2VkIGluIGBnZW9tX3Ntb290aGAgd2l0aCBzbWFsbCBudW1iZXIKb2Ygb2JzZXJ2YXRpb25zIChgbiA8IDEwMDBgKSBpcyBgbWV0aG9kID0gImxvZXNzImAgd2hpY2ggdXNlcyBhIHNtb290aCBsb2NhbCAKcmVncmVzc2lvbi4gTW9yZSBkZXRhaWxzIGFib3V0IHRoZSBhbGdvcml0aG0gdXNlZCBjYW4gYmUgZm91bmQgaW4gYD9sb2Vzc2AuIAoqIExvZXNzIGRvZXMgbm90IHdvcmsgd2VsbCBmb3IgbGFyZ2UgZGF0YXNldHMgKGl04oCZcyAkTyhuXjIpJCBpbiBtZW1vcnkpLCBhbmQgc28KYW4gYWx0ZXJuYXRpdmUgc21vb3RoaW5nIGFsZ29yaXRobSBpcyB1c2VkIHdoZW4gbiBpcyBncmVhdGVyIHRoYW4gMSwwMDAuCgoKIyBFeGVyY2lzZSBJSQoKMS4gUmUtY3JlYXRlIGEgc2NhdHRlciBwbG90IHdpdGggcGVyY2VudCBvZiBwZW9wbGUgYWdlZCAxNSsgd2l0aCBhIGJhbmsgYWNjb3VudApvbiB0aGUgeCBheGlzIGFuZCBTRURBIGN1cnJlbnQgbGV2ZWwgc2NvcmUgb24gdGhlIHkgYXhpcyAKKGFzIHlvdSBkaWQgaW4gdGhlIHByZXZpb3VzIGV4ZXJjaXNlKS4KMi4gT3ZlcmxheSBhIHNtb290aGluZyBsaW5lIG9uIHRvcCBvZiB0aGUgc2NhdHRlciBwbG90IHVzaW5nIHRoZSBsbSBtZXRob2QuIApIaW50OiBzZWUgYD9zdGF0X3Ntb290aGAuCjMuIE92ZXJsYXkgYSBzbW9vdGhpbmcgbGluZSBvbiB0b3Agb2YgdGhlIHNjYXR0ZXIgcGxvdCB1c2luZyB0aGUgZGVmYXVsdCBtZXRob2QuCjQuIE92ZXJsYXkgYSBzbW9vdGhpbmcgbGluZSBvbiB0b3Agb2YgdGhlIHNjYXR0ZXIgcGxvdCB1c2luZyB0aGUgZGVmYXVsdCBsb2VzcyAKbWV0aG9kLCBidXQgbWFrZSBpdCBsZXNzIHNtb290aC4gSGludDogc2VlIGA/bG9lc3NgLgoKYGBge3J9CiMgKC4uLj8pCmBgYAoKCiMgU2NhbGVzCgoKIyMgQWVzdGhldGljIE1hcHBpbmcgVmFyaWFibGUgU2NhbGluZwoKQWVzdGhldGljIG1hcHBpbmcgKGkuZS4sIHdpdGggYWVzKCkpIGlzIHJlc3BvbnNpYmxlIGZvciBhc3NpZ25pbmcgYW4gYWVzdGhldGljIAp0byBhIHZhcmlhYmxlLiBJdCBkb2Vzbid0IGhvd2V2ZXIgc3BlY2lmeSBob3cgbWFwcGluZyBzaG91bGQgYmUgZG9uZS4gCgpGb3IgZXhhbXBsZSwgYGFlcyhzaGFwZSA9IHgpYCBvciBgYWVzKGNvbG9yID0geilgIGRvIG5vdCBzcGVjaWZ5IHdoYXQgc2hhcGVzCm9yIHdoYXQgY29sb3JzIHNob3VsZCBiZSB1c2VkLiBUbyBjaG9vc2UgY29sb3JzL3NoYXBlcy9zaXplcyBldGMuIHlvdSBuZWVkCnRvIG1vZGlmeSB0aGUgY29ycmVzcG9uZGluZyBzY2FsZS4gCgoqKkluIGdncGxvdDIgc2NhbGVzIGluY2x1ZGU6KioKCiogcG9zaXRpb24KKiBjb2xvciBhbmQgZmlsbAoqIHNpemUKKiBzaGFwZQoqIGxpbmUgdHlwZQoKU2NhbGVzIGFyZSBtb2RpZmllZCB3aXRoIGEgc2VyaWVzIG9mIGZ1bmN0aW9ucyB1c2luZyBhIApgc2NhbGVfPGFlc3RoZXRpYz5fPHR5cGU+YCBuYW1pbmcgc2NoZW1lLiBUcnkgdHlwaW5nIGBzY2FsZV88dGFiPmAgdG8gc2VlIAphIGxpc3Qgb2Ygc2NhbGUgbW9kaWZpY2F0aW9uIGZ1bmN0aW9ucy4KCioqQ29tbW9uIFNjYWxlIEFyZ3VtZW50czoqKgoKKiAqKm5hbWUqKjogdGhlIGZpcnN0IGFyZ3VtZW50IGdpdmVzIHRoZSBheGlzIG9yIGxlZ2VuZCB0aXRsZQoqICoqbGltaXRzKio6IHRoZSBtaW5pbXVtIGFuZCBtYXhpbXVtIG9mIHRoZSBzY2FsZQoqICoqYnJlYWtzKio6IHRoZSBwb2ludHMgYWxvbmcgdGhlIHNjYWxlIHdoZXJlIGxhYmVscyBzaG91bGQgYXBwZWFyCiogKipsYWJlbHMqKjogdGhlIGxhYmVscyB0aGF0IGFwcGVhciBhdCBlYWNoIGJyZWFrCgojIyBTY2FsZTogYXhlcwoKU3F1YXJlIHJvb3QgdHJhbnNmb3JtYXRpb24gb2YgdGhlIHktYXhpczoKCmBgYHtyfQpwMSA8LSBnZ3Bsb3QoZHNtYWxsLCBhZXMoeCA9IGNhcmF0LCB5ID0gcHJpY2UpKSAKcDEgKyBnZW9tX3BvaW50KCkgKyBzY2FsZV95X3NxcnQoKQpgYGAKCkxvZyBiYXMgMTAgdHJhbnNmb3JtYXRpb24gb2YgdGhlIHktYXhpczoKCmBgYHtyfQpwMSArIGdlb21fcG9pbnQoKSArIHNjYWxlX3lfbG9nMTAoKQpgYGAKCkxvZyBiYXNlIDEwIHRyYW5zZm9ybWF0aW9uIG9mIHggYW5kIHkgYXhlczoKCmBgYHtyfQpwMSArIGdlb21fcG9pbnQoKSArIHNjYWxlX3lfbG9nMTAoKSArIHNjYWxlX3hfbG9nMTAoKQpgYGAKCk5vdGUgdGhhdCB0aGUgYWJvdmUgcHJvZHVjZXMgdGhlIHNhbWUgcG9pbnRzIGFzOgpgYGB7cn0KZ2dwbG90KGRzbWFsbCwgYWVzKHggPSBsb2coY2FyYXQpLCB5ID0gbG9nKHByaWNlKSkpICsgZ2VvbV9wb2ludCgpCmBgYAoKYnV0IHdpdGggZGlmZmVyZW50IHZhbHVlcyBvbiB0aGUgYXhlcy4KCiMjIFNjYWxlOiBzaGFwZXMKCmBgYHtyfQpwMSArIGdlb21fcG9pbnQoYWVzKHNoYXBlID0gY3V0KSwgc2l6ZSA9IDMpICAKYGBgCgpgYGB7cn0KcDEgKyBnZW9tX3BvaW50KGFlcyhzaGFwZSA9IGN1dCksIHNpemUgPSAzKSArIAogIHNjYWxlX3NoYXBlX21hbnVhbCh2YWx1ZXMgPSBjKDE6NSkpCmBgYAoKIyMgU2NhbGU6IGNvbG9ycwoKVG8gY2hvb3NlIHNwZWNpZmljIGNvbG9ycyBmb3IgKipkaXNjcmV0ZSoqIHZhcmlhYmxlcyB3ZSBjYW4gdXNlIGBzY2FsZV9jb2xvcl9tYW51YWxgCgpgYGB7cn0KcDEgKyBnZW9tX3BvaW50KGFlcyhjb2xvciA9IGN1dCksIHNpemUgPSAzKSArIAogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJyZWQiLCAib3JhbmdlIiwgInllbGxvdyIsICJncmVlbiIsICJibHVlIikpCmBgYAoKRm9yICoqY29udGludW91cyoqIHZhcmlhYmxlcyB5b3UgY2FuIGFsc28gdXNlIGBzY2FsZV9jb2xvcl9ncmFkaWVudGAsIGFuZCBzcGVjaWZ5CnRoZSBlbmRzIG9mIHRoZSBzcGVjdHJ1bToKCmBgYHtyfQpwMSArIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gcHJpY2UpLCBzaXplID0gMykgKyAKICBzY2FsZV9jb2xvcl9ncmFkaWVudChsb3cgPSAiYmx1ZSIsIGhpZ2ggPSAicmVkIikKYGBgCgpgc2NhbGVfY29sb3JfYnJld2VyYCBpcyBhIHZlcnkgdXNlZnVsIGZ1bmN0aW9uIHRoYXQgY2FuIGJlIHVzZWQgdG8gc2V0CmNvbG9ycyBmb3IgKipkaXNjcmV0ZSoqIHZhcmlhYmxlcy4gSXQgZ2l2ZXMgeW91IGEgY2hvaWNlIGJldHdlZW4gbWFueSBwcmVkZWZpbmVkCmFuZCBwcmV0dHkgY29sb3IgcGFsZXR0ZXMuCgpgYGB7cn0KcDEgKyBnZW9tX3BvaW50KGFlcyhjb2xvciA9IGN1dCksIHNpemUgPSAzKSArIAogIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gIlNldDIiKQpgYGAKClVuZm9ydHVuYXRlbHkgYHNjYWxlX2NvbG9yX2JyZXdlcmAgZG9lc24ndCB3b3JrIGZvciBjb250aW51b3VzIHZhcmlhYmxlczoKCmBgYHtyfQpwMSArIGdlb21fcG9pbnQoYWVzKHNoYXBlID0gcHJpY2UpLCBzaXplID0gMykgKyAKICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJTcGVjdHJhbCIpCmBgYAoKVGhhbmtmdWxseSwgd2UgY2FuIGdldCBhcm91bmQgdGhpcyBpc3N1ZSB1c2luZyB0aGUgYFJDb2xvckJyZXdlcmAgcGFja2FnZQphbmQgdXNpbmcgYHNjYWxlX2NvbG9yX2dyYWRpZW50bmA6CgpgYGB7cn0KbGlicmFyeShSQ29sb3JCcmV3ZXIpCnAxICsgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBwcmljZSksIHNpemUgPSAzKSArIAogIHNjYWxlX2NvbG9yX2dyYWRpZW50bihjb2xvdXJzID0gYnJld2VyLnBhbChuYW1lID0gIlNwZWN0cmFsIiwgbiA9IDEwKSkKYGBgCgoKLi4uIGFuZCBpZiB5b3UgYXJlIGEgdHJ1ZSBpbmRpZSBwZXJzb24sIHlvdSBjYW4gaXMgZXZlbiBjaGVjayBvdXQKdGhlIGNvbG9yIHNjaGVtZXMgYmFzZWQgb24gW1dlcyBBbmRlcnNvbl0oaHR0cDovL3dlc2FuZGVyc29ucGFsZXR0ZXMudHVtYmxyLmNvbS8pCm1vdmllczoKCmBgYHtyfQojaW5zdGFsbC5wYWNrYWdlcygid2VzYW5kZXJzb24iKQpsaWJyYXJ5KHdlc2FuZGVyc29uKQpuYW1lcyh3ZXNfcGFsZXR0ZXMpCmBgYAoKRm9yIGRpc2NyZXRlOgoKYGBge3J9CnAxICsgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBjdXQpLCBzaXplID0gMykgKyAKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gd2VzX3BhbGV0dGUoIkRhcmplZWxpbmciLCBuID0gNSkpCmBgYAoKRm9yIGNvbnRpbnVvdXM6CgpgYGB7cn0KcDEgKyBnZW9tX3BvaW50KGFlcyhjb2xvciA9IHByaWNlKSwgc2l6ZSA9IDMpICsgCiAgc2NhbGVfY29sb3JfZ3JhZGllbnRuKGNvbG91cnMgPSB3ZXNfcGFsZXR0ZSgiRGFyamVlbGluZyIsIDEwMCwgdHlwZSA9ICJjb250aW51b3VzIikpCmBgYAoKCgpZb3UgY2FuIGFsc28gc2NhbGUgdGhlIHZhbHVlcyBvZiB0aGUgdmFyaWFibGUgY29ycmVzcG9uZGluZyB0byBjb2xvci4KCmBgYHtyfQpwMSArIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gcHJpY2UpLCBzaXplID0gMykgKyAKICBzY2FsZV9jb2xvcl9ncmFkaWVudChsb3cgPSAiYmx1ZSIsIGhpZ2ggPSAicmVkIiwgdHJhbnMgPSAibG9nMTAiKQpgYGAKCmFuZCBhZGQgeW91ciBvd24gYnJlYWtzIAoKCmBgYHtyfQpwMSArIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gcHJpY2UpLCBzaXplID0gMykgKyAKICBzY2FsZV9jb2xvcl9ncmFkaWVudChsb3cgPSAiYmx1ZSIsIGhpZ2ggPSAicmVkIiwgdHJhbnMgPSAibG9nMTAiLAogICAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IGMoMTAwMCwgMjAwMCwgNTAwMCwgMTAwMDApLAogICAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIiAgMTAwMCIsICIgIDIwMDAiLCAiICA1MDAwIiwgIjEwMDAwIikpIAogIApgYGAKCgojIEV4ZXJjaXNlIElJSQoKMS4gRm9yIHRoZSBzY2F0dGVyIHBsb3Qgb2YgJSBvZiBwcGwgYWdlZCAxNSsgd2l0aCBiYW5rIGFjY291bnQgdnMgU0VEQSBzY29yZQpjb2xvcmVkIGJ5IHJlZ2lvbiwgZ2VuZXJhdGVkIGluIEV4ZXJjaXNlIEkuMyBtb2RpZnkgdGhlIGNvbG9yIHNjYWxlIHRvIAp1c2Ugc3BlY2lmaWMgdmFsdWVzIG9mIHlvdXIgY2hvb3NpbmcuIEhpbnQ6IHNlZSBgP3NjYWxlX2NvbG9yX21hbnVhbGAuCgpgYGB7cn0KIyAoLi4uPykKYGBgCgojIEZhY2V0aW5nCgpgYGB7cn0KcCA8LSAgZ2dwbG90KGRpYW1vbmRzLCBhZXMoeCA9IGNhcmF0KSkKcCArIGdlb21faGlzdG9ncmFtKCkKYGBgCgpgYGB7cn0KcCArIGdlb21faGlzdG9ncmFtKCkgKyBmYWNldF93cmFwKH4gY29sb3IpCmBgYAoKYGBge3IgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9Nn0KcCArIGdlb21faGlzdG9ncmFtKCkgKyBmYWNldF9ncmlkKGN1dCB+IGNvbG9yKQpgYGAKCmBgYHtyfQoocCA8LSBnZ3Bsb3QoZGF0YSA9IGRpYW1vbmRzW3NhbXBsZShucm93KGRpYW1vbmRzKSwgMTAwMCksIF0sIAogICAgICAgICAgICAgYWVzKHggPSBjYXJhdCwgeSA9IHByaWNlKSkgKwogIGdlb21fcG9pbnQoYWVzKHRleHQgPSBwYXN0ZSgiQ2xhcml0eToiLCBjbGFyaXR5KSksIHNpemUgPSAxKSArCiAgZ2VvbV9zbW9vdGgoYWVzKGNvbG91ciA9IGN1dCwgZmlsbCA9IGN1dCkpICsgZmFjZXRfd3JhcCh+IGN1dCkpCmBgYAoKCiMgRXhjZXJjaXNlIElWCjEuIEZhY2V0ICBieSByZWdpb24gKGB+IFJlZ2lvbmApIHRoZSB0aGUgRWNvbm9taXN0IHBsb3QgZnJvbSBFeGVyY2lzZSBJSUkuCgpgYGB7cn0KIyAoLi4uID8pCmBgYAoKCiMgRmluaXNoIHRoZSBFY29ub21pc3QgcGxvdC4KClRvIGNvbXBsZXRlIHRoZSBncmFwaCB3ZSBuZWVkIHRvOgoKKiBhZGQgYSB0cmVuZCBsaW5lCiogY2hhbmdlIHRoZSBheGlzIGxhYmVscwoqIGNoYW5nZSB0aGUgb3JkZXIgb2YgdGhlIFJlZ2lvbiBsYWJlbHMKKiBjaGFuZ2UgdGhlIGNvbG9yaW5nIG9mIHRoZSBwb2ludHMKKiBsYWJlbCBzZWxlY3RlZCBwb2ludHMKKiBjaGFuZ2UgY29sb3IgbGVnZW5kJ3MgcG9zaXRpb24KKiBhZGp1c3QgdGhlIGF4ZXMgcmF0aW8KKiBmaXggdGhlIHRpY2sgbWFya3MKKiBtYXRjaCB0aGUgcGxvdCdzIHRoZW1lIHdpdGggdGhlIEVjb25vbWlzdCB0aGVtZQoqIGFkZCBub3RlcwoKIyMgQ2hhbmdlIG9yZGVyIG9mIHRoZSBSZWdpb25zCgpgYGB7cn0KZGF0JFJlZ2lvbiA8LSBhcy5jaGFyYWN0ZXIoZGF0JFJlZ2lvbikKZGF0JFJlZ2lvbiA8LSBmYWN0b3IoZGF0JFJlZ2lvbiwgCiAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGMoIkV1cm9wZSIsICJBc2lhIiwgIk9jZWFuaWEiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTm9ydGggQW1lcmljYSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJMYXRpbiBBbWVyaWNhICYgdGhlIENhcmliYmVhbiIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJNaWRkbGUgRWFzdCAmIE5vcnRoIEFmcmljYSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlN1Yi1TYWhhcmFuIEFmcmljYSIpLAogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKCJFdXJvcGUiLCAiQXNpYSIsICJPY2VhbmlhIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIk5vcnRoIEFtZXJpY2EiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTGF0aW4gQW1lcmljYSAmIFxuIHRoZSBDYXJpYmJlYW4iLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTWlkZGxlIEVhc3QgJiBcbiBOb3J0aCBBZnJpY2EiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJTdWItU2FoYXJhbiBcbiBBZnJpY2EiKSkKYGBgCgpgYGB7cn0KZ2dwbG90KGRhdCwgYWVzKFBlcmNlbnQub2YuMTVwbHVzLndpdGguYmFuay5hY2NvdW50LCBTRURBLkN1cnJlbnQubGV2ZWwpKSArIAogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gUmVnaW9uKSkKYGBgCgojIEFkZCB0aGUgbGluZWFyIHRyZW5kCgpgYGB7cn0KIyAoLi4uPykKYGBgCgojIyBDaGFuZ2UgdGhlIGF4ZXMgcmF0aW8uCgpIaW50IGA/Y29vcmRfZml4ZWRgCgpgYGB7cn0KIyAoLi4uPykKYGBgCgojIENoYW5nZSB0aGUgY29sb3Igc2NoZW1lCgpVc2UgdGhlIGZvbGxvd2luZyBjb2xvcnMgCmBjKCIjMjhBQURDIiwiI0YyNTgzRiIsICIjNzZDMEMxIiwiIzI0NTc2RCIsICIjMjQ4RTg0IiwiI0RDQzNBQSIsICIjOTY1MDNGIilgCgpgYGB7cn0KIyAoLi4uPykKYGBgCgoKIyMgQWRkIGEgdGl0bGUgYW5kIGZvcm1hdCB0aGUgYXhlcwoKQ2hlY2sgYD9zY2FsZV94X2NvbnRpbnVvdXNgIGFuZCBgP3NjYWxlX3lfY29udGludW91c2AKZG9jdW1lbnRhdGlvbi4gVG8gYWRkIGEgdGl0bGUgdXNlIGBnZ3RpdGxlKClgLgpgYGB7cn0KIyAoLi4uPykKYGBgCgojIyBDaGFuZ2UgdGhlIGJhY2tncm91bmQgYW5kIHRoZW1lCgpZb3UgY2FuIGNoZWNrIG91dCB0aGUgW2BnZ3RoZW1lc2BdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9nZ3RoZW1lcy92aWduZXR0ZXMvZ2d0aGVtZXMuaHRtbCkgCnBhY2thZ2Ugd2hpY2ggaW1wbGVtZW50IHRoZSB0aGVtZXMgdGhhdCBtYWtlIHlvdXIgcGxvdHMgbG9vayBsaWtlIHRoZXkgY2FtZSBmcm9tOgoKKiBCYXNlIGdyYXBoaWNzCiogVGFibGVhdQoqIEV4Y2VsCiogU3RhdGEKKiB0aGUgRWNvbm9taXN0CiogV2FsbCBTdHJlZXQgSm91cm5hbAoqIEVkd2FyZCBUdWZ0ZQoqIE5hdGUgU2lsdmVyJ3MgRml2ZXRoaXJ0eWVpZ2h0CiogZXRjLgoKdXNlIHRoZSBvbmUgdGhhbiBtaW1pY3MgdGhlIEVjb25vbWlzdC4KCmBgYHtyfQpsaWJyYXJ5KGdndGhlbWVzKQojICguLi4/KQpgYGAKCiMjIEZvcm1hdCB0aGUgbGVnZW5kCgpVc2luZyBgdGhlbWUoKWAgYW5kIHRoZSBhcmd1bWVudHMgbGlrZTogCmBsZWdlbmQucG9zaXRpb25gLCBgbGVnZW5kLmRpcmVjdGlvbmAsIGBsZWdlbmQudGV4dGAsIGBwbG90Lm1hcmdpbmAKCmBgYHtyLCBmaWcud2lkdGg9OSwgZmlnLmhlaWdodD01fQojICguLi4/KQpgYGAKCgojIyBBZGQgcG9pbnQgbGFiZWxzCgpBZGQgbGFiZWxzIHRvIHRoZSBmb2xsb3dpbmcgc3Vic2V0IG9mIHRoZSBjb3VudHJpZXM6CgpgYGB7cn0KcG9pbnRzVG9MYWJlbCA8LSBjKCJZZW1lbiIsICJJcmFxIiwgIkVneXB0IiwgIkpvcmRhbiIsICJDaGFkIiwgIkNvbmdvIiwgCiAgICAgICAgICAgICAgICAgICAiQW5nb2xhIiwgIkFsYmFuaWEiLCAiWmltYmFid2UiLCAiVWdhbmRhIiwgIk5pZ2VyaWEiLAogICAgICAgICAgICAgICAgICAgIlVydWd1YXkiLCAiS2F6YWtoc3RhbiIsICJJbmRpYSIsICJUdXJrZXkiLCAiU291dGggQWZyaWNhIiwKICAgICAgICAgICAgICAgICAgICJLZW55YSIsICJSdXNzaWEiLCAiQnJhemlsIiwgIkNoaWxlIiwgIlNhdWRpIEFyYWJpYSIsIAogICAgICAgICAgICAgICAgICAgIlBvbGFuZCIsICJDaGluYSIsICJTZXJiaWEiLCAiVW5pdGVkIFN0YXRlcyIsICJVbml0ZWQgS2luZ2RvbSIpCmBgYAoKVXNlIGBnZW9tX3RleHRfcmVwZWwoKWAKCmBgYHtyLCBmaWcud2lkdGg9OSwgZmlnLmhlaWdodD01fQojICguLi4/KQpgYGAKCiMjIEFkZCBub3RlcyB0byB0aGUgYm90dG9tIGFuZCBzYXZlIHRoZSBwbG90CgpVc2UgYGdyaWQudGV4dCgpYAoKYGBge3J9CiMgKC4uLj8pCmBgYAoKCgojIGBwbG90bHlgIGZvciBpbnRlcmFjdGl2ZSBwbG90dGluZwoKWW91IGNhbiBhbHNvIGVhc2lseSBnZW5lcmF0ZSBhbiBpbnRlcmFjdGl2ZSBwbG90IGJ5IGNhbGxpbmcgYGdncGxvdGx5KClgCmZ1bmN0aW9uIGZyb20gYHBsb3RseWAgcGFja2FnZS4KCmBgYHtyfQojaW5zdGFsbC5wYWNrYWdlcygicGxvdGx5IikKYGBgCgoKYGBge3IsIGZpZy53aWR0aD05LCBmaWcuaGVpZ2h0PTV9CmxpYnJhcnkocGxvdGx5KQojZ2dwbG90bHkocEVjb25vbWlzdCkKYGBgCgoKU2ltaWxhciB0byB0aGUgb3JpZ2luYWw6CgohW10oLi9maWd1cmVzL2Vjb25vbWlzdC5wbmcpCgoKIyBXaGF0IHdlIGhhdmUgbGVhcm5lZCBzbyBmYXI6CgoqIDJEIHBsb3R0aW5nIGluIFIgY2FuIGJlIGRvbmUgd2l0aDoKICAgICsgYmFzZSBncmFwaGljcyBvciBgZ2dwbG90MmAKKiBUaGUgYnVpbGRpbmcgYmxvY2tzIG9mIGBnZ3Bsb3QyYAoqIEhvdyB0byBnZW5lcmF0ZToKICAgICsgbGluZSBwbG90cwogICAgKyBzY2F0dGVyIHBsb3RzCiAgICArIGhpc3RvZ3JhbXMKICAgICsgYmFyIHBsb3RzCiAgICArIGJveHBsb3RzCiAgICArIHByZWRpY3Rpb24vdHJlbmQgbGluZXMgb3Igc21vb3RoZXJzCiogSG93IHRvIG1vZGlmeSB0aGUgYWVzdGhldGljcyBzZXR0aW5nczoKICAgICsgY29sb3Jpbmcgc2NoZW1lCiAgICArIHNoYXBlcwoqIEhvdyB0byB1c2UgdGhlbWVzIHRvIGF1dG9tYXRpY2FsbHkgY2hhbmdlIHRoZSBzdHlsZSBvZiBhIHBsb3QuCiogRmFjZXQgdGhlIHBsb3QgdG8gZGlzcGxheSB0aGUgaW5mb3JtYXRpb24gZm9yIGRpZmZlcmVudCBzdWJzZXRzIG9mIGRhdGEgd2l0aCAKZGlmZmVyZW50IHZhbHVlcyBvZiBhIHNwZWNpZmljIGF0dHJpYnV0ZS4KCiMgQWRkaXRpb25hbCByZXNvdXJjZXM6CgoqIEhhZGxleSBXaWNraGFtJ3MgW1IgZm9yIERhdGEgU2NpZW5jZV0oaHR0cDovL3I0ZHMuaGFkLmNvLm56Lyk6IENoYXB0ZXIgMy4gRGF0YSBWaXN1YWxpemF0aW9uCiogSGFkbGV5IFdpY2toYW0ncyBbZ2dwbG90MjogRWxlZ2FudCBHcmFwaGljcyBmb3IgRGF0YSBBbmFseXNpc10oVXNlIFIhKShodHRwOi8vZ2dwbG90Mi5vcmcvYm9vay8pIAoqIFdpa2k6IGh0dHBzOi8vZ2l0aHViLmNvbS9oYWRsZXkvZ2dwbG90Mi93aWtpCiogYHBsb3RseWAgW2dpdGh1Yl0oaHR0cHM6Ly9naXRodWIuY29tL3JvcGVuc2NpL3Bsb3RseSkK